add finite state machine to control/manage transport state

This commit is contained in:
Paul Davis 2019-09-17 18:26:03 -06:00
parent fc3e7623e7
commit bd229936ec
41 changed files with 899 additions and 452 deletions

View File

@ -383,7 +383,7 @@ ExportDialog::show_progress ()
}
}
status->finish ();
status->finish (TRS_UI);
if (!status->aborted() && UIConfiguration::instance().get_save_export_mixer_screenshot ()) {
ExportProfileManager::TimespanStateList const& timespans = profile_manager->get_timespans();

View File

@ -793,7 +793,7 @@ ExportVideoDialog::launch_export ()
}
}
audio_progress_connection.disconnect();
status->finish ();
status->finish (TRS_UI);
if (status->aborted()) {
::g_unlink (_insnd.c_str());
delete _transcoder; _transcoder = 0;

View File

@ -24,8 +24,6 @@
#include <map>
#include <string>
#include <boost/variant.hpp>
#include "pbd/xml++.h"
#include "pbd/id.h"

View File

@ -34,7 +34,7 @@ namespace PBD {
namespace DEBUG {
LIBARDOUR_API extern DebugBits MidiSourceIO;
LIBARDOUR_API extern DebugBits MidiPlaylistIO;
LIBARDOUR_API extern DebugBits MidiDiskstreamIO;
LIBARDOUR_API extern DebugBits MidiDiskIO;
LIBARDOUR_API extern DebugBits MidiRingBuffer;
LIBARDOUR_API extern DebugBits SnapBBT;
LIBARDOUR_API extern DebugBits Latency;
@ -49,6 +49,8 @@ namespace PBD {
LIBARDOUR_API extern DebugBits LTC;
LIBARDOUR_API extern DebugBits TXLTC;
LIBARDOUR_API extern DebugBits Transport;
LIBARDOUR_API extern DebugBits TFSMEvents;
LIBARDOUR_API extern DebugBits TFSMState;
LIBARDOUR_API extern DebugBits Slave;
LIBARDOUR_API extern DebugBits SessionEvents;
LIBARDOUR_API extern DebugBits MidiIO;

View File

@ -117,8 +117,6 @@ protected:
protected:
Flag _flags;
uint32_t i_am_the_modifier;
double _actual_speed;
double _target_speed;
bool _slaved;
bool in_set_state;
samplepos_t playback_sample;

View File

@ -97,8 +97,17 @@ public:
static void set_midi_readahead_samples (samplecnt_t samples_ahead) { midi_readahead = samples_ahead; }
static void set_no_disk_output (bool yn);
static bool no_disk_output() { return _no_disk_output; }
/* inc/dec variants MUST be called as part of the process call tree, before any
disk readers are invoked. We use it when the session needs the
transport (and thus effective read position for DiskReaders) to keep
advancing as part of syncing up with a transport master, but we
don't want any actual disk output yet because we are still not
synced.
*/
static void inc_no_disk_output () { g_atomic_int_inc (&_no_disk_output); }
static void dec_no_disk_output();
static bool no_disk_output () { return g_atomic_int_get (&_no_disk_output); }
protected:
friend class Track;
@ -156,7 +165,7 @@ private:
static samplecnt_t _chunk_samples;
static samplecnt_t midi_readahead;
static bool _no_disk_output;
static gint _no_disk_output;
int audio_read (PBD::PlaybackBuffer<Sample>*,
Sample* sum_buffer,

View File

@ -54,8 +54,8 @@ class LIBARDOUR_API ExportStatus {
}
Glib::Threads::Mutex& lock () { return _run_lock; }
PBD::Signal0<void> Finished;
void finish ();
PBD::Signal1<void,TransportRequestSource> Finished;
void finish (TransportRequestSource);
void cleanup ();

View File

@ -88,7 +88,7 @@
#include "ardour/presentation_info.h"
#include "ardour/route.h"
#include "ardour/route_graph.h"
#include "ardour/transport_api.h"
class XMLTree;
class XMLNode;
@ -167,6 +167,7 @@ class Source;
class Speakers;
class TempoMap;
class TransportMaster;
struct TransportFSM;
class Track;
class UI_TransportMaster;
class VCAManager;
@ -186,7 +187,7 @@ private:
};
/** Ardour Session */
class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionList, public SessionEventManager
class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionList, public SessionEventManager, public TransportAPI
{
private:
@ -457,7 +458,8 @@ public:
void adjust_capture_buffering();
bool global_locate_pending() const { return _global_locate_pending; }
bool locate_pending() const { return static_cast<bool>(post_transport_work()&PostTransportLocate); }
bool locate_pending() const;
bool declick_in_progress () const;
bool transport_locked () const;
int wipe ();
@ -752,8 +754,8 @@ public:
return 0;
}
double transport_speed() const { return _count_in_samples > 0 ? 0. : _transport_speed; }
bool transport_stopped() const { return _transport_speed == 0.0; }
bool transport_rolling() const { return _transport_speed != 0.0 && _count_in_samples == 0 && _remaining_latency_preroll == 0; }
bool transport_stopped() const;
bool transport_rolling() const;
bool silent () { return _silent; }
@ -1237,6 +1239,16 @@ protected:
friend class Route;
void update_latency_compensation (bool force = false);
/* transport API */
void locate (samplepos_t, bool with_roll, bool with_flush, bool with_loop=false, bool force=false, bool with_mmc=true);
void stop_transport (bool abort = false, bool clear_state = false);
void start_transport ();
void butler_completed_transport_work ();
void post_locate ();
void schedule_butler_for_transport_work ();
bool should_roll_after_locate () const;
private:
int create (const std::string& mix_template, BusProfile*);
void destroy ();
@ -1262,7 +1274,6 @@ private:
samplecnt_t _base_sample_rate; // sample-rate of the session at creation time, "native" SR
samplecnt_t _nominal_sample_rate; // overridden by audioengine setting
samplecnt_t _current_sample_rate; // this includes video pullup offset
int transport_sub_state;
mutable gint _record_status;
samplepos_t _transport_sample;
gint _seek_counter;
@ -1357,7 +1368,7 @@ private:
int pre_export ();
int stop_audio_export ();
void finalize_audio_export ();
void finalize_audio_export (TransportRequestSource trs);
void finalize_export_internal (bool stop_freewheel);
bool _pre_export_mmc_enabled;
@ -1436,11 +1447,11 @@ private:
Butler* _butler;
boost::shared_ptr<TransportFSM> _transport_fsm;
static const PostTransportWork ProcessCannotProceedMask =
PostTransportWork (
PostTransportReverse|
PostTransportAudition|
PostTransportStop|
PostTransportClearSubstate);
gint _post_transport_work; /* accessed only atomic ops */
@ -1671,19 +1682,16 @@ private:
int start_midi_thread ();
bool should_ignore_transport_request (TransportRequestSource, TransportRequestType) const;
bool declick_in_progress () const;
void set_play_loop (bool yn, double speed);
void unset_play_loop ();
void overwrite_some_buffers (Track *);
void flush_all_inserts ();
int micro_locate (samplecnt_t distance);
void locate (samplepos_t, bool with_roll, bool with_flush, bool with_loop=false, bool force=false, bool with_mmc=true);
void start_locate (samplepos_t, bool with_roll, bool with_flush, bool for_loop_enabled=false, bool force=false);
void do_locate (samplepos_t, bool with_roll, bool with_flush, bool for_loop_enabled, bool force, bool with_mmc);
void force_locate (samplepos_t sample, bool with_roll = false);
void set_transport_speed (double speed, samplepos_t destination_sample, bool abort = false, bool clear_state = false, bool as_default = false);
void stop_transport (bool abort = false, bool clear_state = false);
void start_transport ();
void realtime_stop (bool abort, bool clear_state);
void realtime_locate ();
void non_realtime_start_scrub ();
@ -1691,8 +1699,8 @@ private:
void non_realtime_locate ();
void non_realtime_stop (bool abort, int entry_request_count, bool& finished);
void non_realtime_overwrite (int entry_request_count, bool& finished);
void post_transport ();
void engine_halted ();
void engine_running ();
void xrun_recovery ();
void set_track_loop (bool);
bool select_playhead_priority_target (samplepos_t&);

View File

@ -63,7 +63,6 @@ public:
/* only one of each of these events can be queued at any one time */
StopOnce,
AutoLoop,
};

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2019 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
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_transport_api_h_
#define _ardour_transport_api_h_
#include "ardour/types.h"
#include "ardour/libardour_visibility.h"
namespace ARDOUR
{
class LIBARDOUR_API TransportAPI
{
public:
TransportAPI() {}
virtual ~TransportAPI() {}
private:
friend struct TransportFSM;
virtual void locate (samplepos_t, bool with_roll, bool with_flush, bool with_loop=false, bool force=false, bool with_mmc=true) = 0;
virtual void stop_transport (bool abort = false, bool clear_state = false) = 0;
virtual void start_transport () = 0;
virtual void butler_completed_transport_work () = 0;
virtual void schedule_butler_for_transport_work () = 0;
virtual bool should_roll_after_locate () const = 0;
};
} /* end namespace ARDOUR */
#endif

View File

@ -0,0 +1,213 @@
#ifndef _ardour_transport_fsm_h_
#define _ardour_transport_fsm_h_
#include <boost/weak_ptr.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/back/tools.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>
#include "pbd/demangle.h"
#include "pbd/stacktrace.h"
#include "ardour/debug.h"
/* state machine */
namespace msm = boost::msm;
namespace mpl = boost::mpl;
namespace ARDOUR
{
class TransportAPI;
struct TransportFSM : public msm::front::state_machine_def<TransportFSM>
{
/* events to be delivered to the FSM */
struct butler_done {};
struct butler_required {};
struct declick_done {};
struct start_transport {};
struct stop_transport {
stop_transport (bool ab = false, bool cl = false)
: abort (ab)
, clear_state (cl) {}
bool abort;
bool clear_state;
};
struct locate {
locate ()
: target (0)
, with_roll (false)
, with_flush (false)
, with_loop (false)
, force (false) {}
locate (samplepos_t target, bool roll, bool flush, bool loop, bool f4c)
: target (target)
, with_roll (roll)
, with_flush (flush)
, with_loop (loop)
, force (f4c) {}
samplepos_t target;
bool with_roll;
bool with_flush;
bool with_loop;
bool force;
};
struct locate_done {};
/* Flags */
struct DeclickInProgress {};
struct LocateInProgress {};
struct IsRolling {};
struct IsStopped {};
struct IsWaitingForButler {};
typedef msm::active_state_switch_before_transition active_state_switch_policy;
/* transition actions */
void start_playback (start_transport const& p);
void roll_after_locate (locate_done const& p);
void stop_playback (declick_done const& s);
void start_locate (locate const& s);
void start_saved_locate (declick_done const& s);
void interrupt_locate (locate const& s);
void schedule_butler_for_transport_work (butler_required const&);
void save_locate_and_start_declick (locate const &);
void start_declick (stop_transport const &);
/* guards */
bool should_roll_after_locate (locate_done const &);
bool should_not_roll_after_locate (locate_done const & e) { return !should_roll_after_locate (e); }
#define define_state(State) \
struct State : public msm::front::state<> \
{ \
template <class Event,class FSM> void on_entry (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "entering: " # State "\n"); } \
template <class Event,class FSM> void on_exit (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "leaving: " # State "\n"); } \
}
#define define_state_flag(State,Flag) \
struct State : public msm::front::state<> \
{ \
template <class Event,class FSM> void on_entry (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "entering: " # State "\n"); } \
template <class Event,class FSM> void on_exit (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "leaving: " # State "\n"); } \
typedef mpl::vector1<Flag> flag_list; \
}
#define define_state_flag2(State,Flag1,Flag2) \
struct State : public msm::front::state<> \
{ \
template <class Event,class FSM> void on_entry (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "entering: " # State "\n"); } \
template <class Event,class FSM> void on_exit (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "leaving: " # State "\n"); } \
typedef mpl::vector2<Flag1,Flag2> flag_list; \
}
/* FSM states */
define_state_flag (WaitingForButler, IsWaitingForButler);
define_state (NotWaitingForButler);
define_state_flag (Stopped,IsStopped);
define_state_flag (Rolling,IsRolling);
define_state_flag (DeclickToLocate,DeclickInProgress);
define_state_flag (WaitingForLocate,LocateInProgress);
define_state_flag (DeclickToStop,DeclickInProgress);
// Pick a back-end
typedef msm::back::state_machine<TransportFSM> back;
boost::weak_ptr<back> wp;
bool locating () { return backend()->is_flag_active<LocateInProgress>(); }
bool locating (declick_done const &) { return locating(); }
bool rolling () { return backend()->is_flag_active<IsRolling>(); }
bool stopped () { return backend()->is_flag_active<IsStopped>(); }
bool waiting_for_butler() { return backend()->is_flag_active<IsWaitingForButler>(); }
bool declick_in_progress() { return backend()->is_flag_active<DeclickInProgress>(); }
static boost::shared_ptr<back> create(TransportAPI& api) {
boost::shared_ptr<back> p (new back ());
p->wp = p;
p->api = &api;
return p;
}
boost::shared_ptr<back> backend() { return wp.lock(); }
template<typename Event> void enqueue (Event const & e) {
backend()->process_event (e);
}
/* the initial state */
typedef boost::mpl::vector<Stopped,NotWaitingForButler> initial_state;
/* transition table */
typedef TransportFSM T; // makes transition table cleaner
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +----------------------+----------------+------------------+---------------------+----------------------+
a_row < Stopped, start_transport, Rolling, &T::start_playback >,
_row < Stopped, stop_transport, Stopped >,
a_row < Stopped, locate, WaitingForLocate, &T::start_locate >,
g_row < WaitingForLocate, locate_done, Stopped, &T::should_not_roll_after_locate >,
_row < Rolling, butler_done, Rolling >,
_row < Rolling, start_transport, Rolling >,
a_row < Rolling, stop_transport, DeclickToStop, &T::start_declick >,
a_row < DeclickToStop, declick_done, Stopped, &T::stop_playback >,
a_row < Rolling, locate, DeclickToLocate, &T::save_locate_and_start_declick >,
a_row < DeclickToLocate, declick_done, WaitingForLocate, &T::start_saved_locate >,
row < WaitingForLocate, locate_done, Rolling, &T::roll_after_locate, &T::should_roll_after_locate >,
a_row < NotWaitingForButler, butler_required, WaitingForButler, &T::schedule_butler_for_transport_work >,
a_row < WaitingForButler, butler_required, WaitingForButler, &T::schedule_butler_for_transport_work >,
_row < WaitingForButler, butler_done, NotWaitingForButler >,
a_row < WaitingForLocate, locate, WaitingForLocate, &T::interrupt_locate >,
a_row < DeclickToLocate, locate, DeclickToLocate, &T::interrupt_locate >,
// Deferrals
#define defer(start_state,ev) boost::msm::front::Row<start_state, ev, start_state, boost::msm::front::Defer, boost::msm::front::none >
defer (DeclickToLocate, start_transport),
defer (DeclickToLocate, stop_transport),
defer (DeclickToStop, start_transport),
defer (WaitingForLocate, start_transport),
defer (WaitingForLocate, stop_transport)
#undef defer
> {};
typedef int activate_deferred_events;
locate _last_locate;
stop_transport _last_stop;
TransportAPI* api;
// Replaces the default no-transition response.
template <class FSM,class Event>
void no_transition(Event const& e, FSM&,int state)
{
typedef typename boost::msm::back::recursive_get_transition_table<FSM>::type recursive_stt;
typedef typename boost::msm::back::generate_state_set<recursive_stt>::type all_states;
std::string stateName;
boost::mpl::for_each<all_states,boost::msm::wrap<boost::mpl::placeholders::_1> >(boost::msm::back::get_state_name<recursive_stt>(stateName, state));
std::cout << "No transition from state: " << PBD::demangle (stateName) << " on event " << typeid(e).name() << std::endl;
}
};
} /* end namespace ARDOUR */
#endif

View File

@ -346,8 +346,6 @@ class LIBARDOUR_API TransportMaster : public PBD::Stateful {
TransportRequestType request_mask() const { return _request_mask; }
void set_request_mask (TransportRequestType);
void get_current (double&, samplepos_t&, samplepos_t);
/* this is set at construction, and not changeable later, so it is not
* a property
*/
@ -358,6 +356,8 @@ class LIBARDOUR_API TransportMaster : public PBD::Stateful {
std::string display_name (bool sh/*ort*/ = true) const;
virtual void unregister_port ();
void connect_port_using_state ();
virtual void create_port () = 0;
protected:
SyncSource _type;
@ -385,6 +385,8 @@ class LIBARDOUR_API TransportMaster : public PBD::Stateful {
boost::shared_ptr<Port> _port;
XMLNode port_node;
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);
@ -452,6 +454,8 @@ class LIBARDOUR_API MTC_TransportMaster : public TimecodeTransportMaster, public
std::string position_string() const;
std::string delta_string() const;
void create_port ();
private:
PBD::ScopedConnectionList port_connections;
PBD::ScopedConnection config_connection;
@ -518,6 +522,8 @@ public:
std::string position_string() const;
std::string delta_string() const;
void create_port ();
private:
void parse_ltc(const pframes_t, const Sample* const, const samplecnt_t);
void process_ltc(samplepos_t const);
@ -586,6 +592,8 @@ class LIBARDOUR_API MIDIClock_TransportMaster : public TransportMaster, public T
float bpm() const { return _bpm; }
void create_port ();
protected:
PBD::ScopedConnectionList port_connections;
@ -648,6 +656,8 @@ class LIBARDOUR_API Engine_TransportMaster : public TransportMaster
std::string position_string() const;
std::string delta_string() const;
void create_port () { }
private:
AudioEngine& engine;
bool _starting;

View File

@ -33,6 +33,7 @@ class UI_TransportMaster;
class LIBARDOUR_API TransportMasterManager : public boost::noncopyable
{
public:
static TransportMasterManager& create ();
~TransportMasterManager ();
int set_default_configuration ();
@ -82,6 +83,8 @@ class LIBARDOUR_API TransportMasterManager : public boost::noncopyable
static const std::string state_node_name;
void reconnect_ports ();
private:
TransportMasterManager();

View File

@ -478,8 +478,8 @@ Bundle::connected_to (boost::shared_ptr<Bundle> other, AudioEngine & engine,
Bundle::PortList const & other_ports =
other->channel_ports (other->type_channel_to_overall(type, i));
for (Bundle::PortList::const_iterator j = our_ports.begin();
j != our_ports.end(); ++j) {
for (Bundle::PortList::const_iterator j = our_ports.begin(); j != our_ports.end(); ++j) {
boost::shared_ptr<Port> p = engine.get_port_by_name(*j);
for (Bundle::PortList::const_iterator k = other_ports.begin();

View File

@ -29,7 +29,7 @@ using namespace std;
PBD::DebugBits PBD::DEBUG::MidiSourceIO = PBD::new_debug_bit ("midisourceio");
PBD::DebugBits PBD::DEBUG::MidiPlaylistIO = PBD::new_debug_bit ("midiplaylistio");
PBD::DebugBits PBD::DEBUG::MidiDiskstreamIO = PBD::new_debug_bit ("mididiskstreamio");
PBD::DebugBits PBD::DEBUG::MidiDiskIO = PBD::new_debug_bit ("mididiskio");
PBD::DebugBits PBD::DEBUG::MidiRingBuffer = PBD::new_debug_bit ("midiringbuffer");
PBD::DebugBits PBD::DEBUG::SnapBBT = PBD::new_debug_bit ("snapbbt");
PBD::DebugBits PBD::DEBUG::Latency = PBD::new_debug_bit ("latency");
@ -44,6 +44,8 @@ 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::TFSMEvents = PBD::new_debug_bit ("tfsmevents");
PBD::DebugBits PBD::DEBUG::TFSMState = PBD::new_debug_bit ("tfsmstate");
PBD::DebugBits PBD::DEBUG::Slave = PBD::new_debug_bit ("slave");
PBD::DebugBits PBD::DEBUG::SessionEvents = PBD::new_debug_bit ("sessionevents");
PBD::DebugBits PBD::DEBUG::MidiIO = PBD::new_debug_bit ("midiio");

View File

@ -51,7 +51,7 @@ Sample* DiskReader::_sum_buffer = 0;
Sample* DiskReader::_mixdown_buffer = 0;
gain_t* DiskReader::_gain_buffer = 0;
samplecnt_t DiskReader::midi_readahead = 4096;
bool DiskReader::_no_disk_output = false;
gint DiskReader::_no_disk_output (0);
DiskReader::DiskReader (Session& s, string const & str, DiskIOProcessor::Flag f)
: DiskIOProcessor (s, str, f)
@ -74,8 +74,8 @@ void
DiskReader::ReaderChannelInfo::resize (samplecnt_t bufsize)
{
delete rbuf;
/* touch memory to lock it */
rbuf = new PlaybackBuffer<Sample> (bufsize);
/* touch memory to lock it */
memset (rbuf->buffer(), 0, sizeof (Sample) * rbuf->bufsize());
}
@ -262,13 +262,14 @@ DiskReader::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp
}
}
const gain_t target_gain = (speed == 0.0 || ((ms & MonitoringDisk) == 0)) ? 0.0 : 1.0;
const bool declick_out = _session.declick_in_progress();
const gain_t target_gain = (declick_out || (speed == 0.0) || ((ms & MonitoringDisk) == 0)) ? 0.0 : 1.0;
if (!_session.cfg ()->get_use_transport_fades ()) {
_declick_amp.set_gain (target_gain);
}
if ((speed == 0.0) && (ms == MonitoringDisk) && _declick_amp.gain () == target_gain) {
if (declick_out && (ms == MonitoringDisk) && _declick_amp.gain () == target_gain) {
/* no channels, or stopped. Don't accidentally pass any data
* from disk into our outputs (e.g. via interpolation)
*/
@ -348,13 +349,13 @@ DiskReader::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp
if (can_internal_playback_seek (start_sample - playback_sample)) {
internal_playback_seek (start_sample - playback_sample);
} else {
cerr << owner()->name() << " playback not possible: ss = " << start_sample << " ps = " << playback_sample << endl;
cerr << owner()->name() << " playback at " << speed << " not possible: ss = " << start_sample << " ps = " << playback_sample << endl;
abort (); // XXX -- now what?
goto midi;
/*NOTREACHED*/
}
}
if (speed != 0.0) {
if (!declick_out) {
const samplecnt_t total = chaninfo->rbuf->read (disk_buf.data(), disk_samples_to_consume);
if (disk_samples_to_consume > total) {
cerr << _name << " Need " << disk_samples_to_consume << " total = " << total << endl;
@ -384,7 +385,7 @@ DiskReader::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp
/* MIDI data handling */
midi:
if (/*!_session.declick_out_pending() && */ bufs.count().n_midi() && _midi_buf) {
if (!declick_in_progress() && bufs.count().n_midi() && _midi_buf) {
MidiBuffer* dst;
if (_no_disk_output) {
@ -482,10 +483,8 @@ DiskReader::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp
}
bool
DiskReader::declick_in_progress () const {
/* TODO use an atomic-get.
* this may be called from the butler thread
*/
DiskReader::declick_in_progress () const
{
return _declick_amp.gain() != 0; // declick-out
}
@ -544,8 +543,6 @@ DiskReader::overwrite_existing_buffers ()
samplepos_t start = overwrite_sample;
samplecnt_t to_read = size;
cerr << owner()->name() << " over-read: " << to_read << endl;
if (audio_read ((*chan)->rbuf, sum_buffer.get(), mixdown_buffer.get(), gain_buffer.get(), start, to_read, n, reversed)) {
error << string_compose(_("DiskReader %1: when refilling, cannot read %2 from playlist at sample %3"), id(), size, overwrite_sample) << endmsg;
goto midi;
@ -1167,7 +1164,7 @@ DiskReader::get_midi_playback (MidiBuffer& dst, samplepos_t start_sample, sample
Location* loc = _loop_location;
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose (
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose (
"%1 MDS pre-read read %8 offset = %9 @ %4..%5 from %2 write to %3, LOOPED ? %6 .. %7\n", _name,
_midi_buf->get_read_ptr(), _midi_buf->get_write_ptr(), start_sample, end_sample,
(loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes, Port::port_offset()));
@ -1184,7 +1181,7 @@ DiskReader::get_midi_playback (MidiBuffer& dst, samplepos_t start_sample, sample
Evoral::Range<samplepos_t> loop_range (loc->start(), loc->end() - 1);
effective_start = loop_range.squish (start_sample);
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("looped, effective start adjusted to %1\n", effective_start));
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("looped, effective start adjusted to %1\n", effective_start));
if (effective_start == loc->start()) {
/* We need to turn off notes that may extend
@ -1208,23 +1205,23 @@ DiskReader::get_midi_playback (MidiBuffer& dst, samplepos_t start_sample, sample
first = loc->end() - effective_start;
second = nframes - first;
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read for eff %1 end %2: %3 and %4, cycle offset %5\n",
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("loop read for eff %1 end %2: %3 and %4, cycle offset %5\n",
effective_start, loc->end(), first, second));
if (first) {
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #1, from %1 for %2\n",
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("loop read #1, from %1 for %2\n",
effective_start, first));
events_read = _midi_buf->read (*target, effective_start, first);
}
if (second) {
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #2, from %1 for %2\n",
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("loop read #2, from %1 for %2\n",
loc->start(), second));
events_read += _midi_buf->read (*target, loc->start(), second);
}
} else {
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #3, adjusted start as %1 for %2\n",
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("loop read #3, adjusted start as %1 for %2\n",
effective_start, nframes));
events_read = _midi_buf->read (*target, effective_start, effective_start + nframes);
}
@ -1233,11 +1230,11 @@ DiskReader::get_midi_playback (MidiBuffer& dst, samplepos_t start_sample, sample
if (n_skipped > 0) {
warning << string_compose(_("MidiDiskstream %1: skipped %2 events, possible underflow"), id(), n_skipped) << endmsg;
}
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("playback buffer read, from %1 to %2 (%3)", start_sample, end_sample, nframes));
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("playback buffer read, from %1 to %2 (%3)", start_sample, end_sample, nframes));
events_read = _midi_buf->read (*target, start_sample, end_sample, Port::port_offset ());
}
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose (
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose (
"%1 MDS events read %2 range %3 .. %4 rspace %5 wspace %6 r@%7 w@%8\n",
_name, events_read, playback_sample, playback_sample + nframes,
_midi_buf->read_space(), _midi_buf->write_space(),
@ -1246,7 +1243,7 @@ DiskReader::get_midi_playback (MidiBuffer& dst, samplepos_t start_sample, sample
g_atomic_int_add (&_samples_read_from_ringbuffer, nframes);
if (ms & MonitoringInput) {
if (!_no_disk_output && (ms & MonitoringInput)) {
dst.merge_from (*target, nframes);
}
@ -1285,7 +1282,7 @@ DiskReader::midi_read (samplepos_t& start, samplecnt_t dur, bool reversed)
assert(_midi_buf);
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS::midi_read @ %1 cnt %2\n", start, dur));
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("MDS::midi_read @ %1 cnt %2\n", start, dur));
boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack>(_route);
MidiChannelFilter* filter = mt ? &mt->playback_filter() : 0;
@ -1330,7 +1327,7 @@ DiskReader::midi_read (samplepos_t& start, samplecnt_t dur, bool reversed)
this_read = min (dur,this_read);
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS ::read at %1 for %2 loffset %3\n", effective_start, this_read, loop_offset));
DEBUG_TRACE (DEBUG::MidiDiskIO, string_compose ("MDS ::read at %1 for %2 loffset %3\n", effective_start, this_read, loop_offset));
if (midi_playlist()->read (*_midi_buf, effective_start, this_read, loop_range, 0, filter) != this_read) {
error << string_compose(
@ -1419,16 +1416,24 @@ DiskReader::refill_midi ()
}
void
DiskReader::set_no_disk_output (bool yn)
DiskReader::dec_no_disk_output ()
{
/* this MUST be called as part of the process call tree, before any
disk readers are invoked. We use it when the session needs the
transport (and thus effective read position for DiskReaders) to keep
advancing as part of syncing up with a transport master, but we
don't want any actual disk output yet because we are still not
synced.
/* this is called unconditionally when things happen that ought to end
a period of "no disk output". It's OK for that to happen when there
was no corresponding call to ::inc_no_disk_output(), but we must
stop the value from becoming negative.
*/
_no_disk_output = yn;
do {
gint v = g_atomic_int_get (&_no_disk_output);
if (v > 0) {
if (g_atomic_int_compare_and_exchange (&_no_disk_output, v, v - 1)) {
break;
}
} else {
break;
}
} while (true);
}
DiskReader::DeclickAmp::DeclickAmp (samplecnt_t sample_rate)
@ -1459,7 +1464,6 @@ DiskReader::DeclickAmp::apply_gain (AudioBuffer& buf, samplecnt_t n_samples, con
uint32_t offset = 0;
while (remain > 0) {
uint32_t n_proc = remain > max_nproc ? max_nproc : remain;
std::cerr << "g = " << g << std::endl;
for (uint32_t i = 0; i < n_proc; ++i) {
buffer[offset + i] *= g;
}

View File

@ -448,7 +448,6 @@ setup_enum_writer ()
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);

View File

@ -66,11 +66,11 @@ ExportStatus::abort (bool error_occurred)
}
void
ExportStatus::finish ()
ExportStatus::finish (TransportRequestSource trs)
{
Glib::Threads::Mutex::Lock l (_run_lock);
set_running (false);
Finished(); /* EMIT SIGNAL */
Finished (trs); /* EMIT SIGNAL */
}
} // namespace ARDOUR

View File

@ -574,6 +574,7 @@ ARDOUR::init (bool use_windows_vst, bool try_optimization, const char* localedir
PannerManager::instance().discover_panners();
ARDOUR::AudioEngine::create ();
TransportMasterManager::create ();
/* it is unfortunate that we need to include reserved names here that
refer to control surfaces. But there's no way to ensure a complete
@ -619,14 +620,13 @@ ARDOUR::init_post_engine (uint32_t start_cnt)
/* find plugins */
ARDOUR::PluginManager::instance().refresh (!Config->get_discover_vst_on_start());
}
if (start_cnt == 0) {
if ((node = Config->control_protocol_state()) != 0) {
ControlProtocolManager::instance().set_state (*node, 0 /* here: global-config state */);
}
}
if (start_cnt > 0) {
TransportMasterManager::instance().restart ();
}
}

View File

@ -64,10 +64,6 @@ LTC_TransportMaster::LTC_TransportMaster (std::string const & name)
, a3e_timecode (Timecode::timecode_24)
, samples_per_timecode_frame (0)
{
if ((_port = AudioEngine::instance()->register_input_port (DataType::AUDIO, string_compose ("%1 in", _name, false, TransportMasterPort))) == 0) {
throw failed_constructor();
}
DEBUG_TRACE (DEBUG::Slave, string_compose ("LTC registered %1\n", _port->name()));
memset (&prev_frame, 0, sizeof(LTCFrameExt));
@ -84,6 +80,14 @@ LTC_TransportMaster::init ()
reset (true);
}
void
LTC_TransportMaster::create_port ()
{
if ((_port = AudioEngine::instance()->register_input_port (DataType::AUDIO, string_compose ("%1 in", _name, false, TransportMasterPort))) == 0) {
throw failed_constructor();
}
}
void
LTC_TransportMaster::set_session (Session *s)
{

View File

@ -58,9 +58,6 @@ MIDIClock_TransportMaster::MIDIClock_TransportMaster (std::string const & name,
, _running (false)
, _bpm (0)
{
if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
throw failed_constructor();
}
}
MIDIClock_TransportMaster::~MIDIClock_TransportMaster()
@ -75,6 +72,14 @@ MIDIClock_TransportMaster::init ()
current.reset ();
}
void
MIDIClock_TransportMaster::create_port ()
{
if ((_port = create_midi_port (string_compose ("%1 in", _name))) == 0) {
throw failed_constructor();
}
}
void
MIDIClock_TransportMaster::set_session (Session *session)
{

View File

@ -68,10 +68,6 @@ MTC_TransportMaster::MTC_TransportMaster (std::string const & name)
, busy_guard2 (0)
, printed_timecode_warning (false)
{
if ((_port = create_midi_port (string_compose ("%1 in", name))) == 0) {
throw failed_constructor();
}
DEBUG_TRACE (DEBUG::Slave, string_compose ("MTC registered %1\n", _port->name()));
init ();
@ -103,6 +99,14 @@ MTC_TransportMaster::init ()
reset (true);
}
void
MTC_TransportMaster::create_port ()
{
if ((_port = create_midi_port (string_compose ("%1 in", _name))) == 0) {
throw failed_constructor();
}
}
void
MTC_TransportMaster::set_session (Session *s)
{

View File

@ -1788,6 +1788,8 @@ Playlist::region_changed (const PropertyChange& what_changed, boost::shared_ptr<
notify_region_end_trimmed (region);
} else if (what_changed.contains (Properties::position) && what_changed.contains (Properties::length)) {
notify_region_start_trimmed (region);
} else if (what_changed.contains (Properties::start)) {
notify_contents_changed ();
}
/* don't notify about layer changes, since we are the only object that can initiate

View File

@ -645,7 +645,7 @@ Port::set_state (const XMLNode& node, int)
/*static*/ void
Port::set_speed_ratio (double s) {
/* see VMResampler::set_rratio() for min/max range */
_speed_ratio = std::min ((double) Config->get_max_transport_speed(), std::max (0.5, s));
_speed_ratio = std::min ((double) Config->get_max_transport_speed(), std::max (0.5, fabs (s)));
}
/*static*/ void

View File

@ -51,8 +51,7 @@ RTTaskList::drop_threads ()
for (uint32_t i = 0; i < nt; ++i) {
_task_run_sem.signal ();
}
for (std::vector<pthread_t>::const_iterator i = _threads.begin (); i != _threads.end (); ++i)
{
for (std::vector<pthread_t>::const_iterator i = _threads.begin (); i != _threads.end (); ++i) {
pthread_join (*i, NULL);
}
_threads.clear ();
@ -122,7 +121,7 @@ RTTaskList::run ()
boost::function<void ()> to_run;
tm.acquire ();
if (_tasklist.size () > 0) {
if (!_tasklist.empty ()) {
to_run = _tasklist.front();
_tasklist.pop_front ();
}
@ -145,20 +144,29 @@ void
RTTaskList::process (TaskList const& tl)
{
Glib::Threads::Mutex::Lock pm (_process_mutex);
Glib::Threads::Mutex::Lock tm (_tasklist_mutex, Glib::Threads::NOT_LOCK);
tm.acquire ();
_tasklist = tl;
tm.release ();
process_tasklist ();
tm.acquire ();
_tasklist.clear ();
tm.release ();
}
void
RTTaskList::process_tasklist ()
{
if (0 == g_atomic_int_get (&_threads_active) || _threads.size () == 0) {
// if (0 == g_atomic_int_get (&_threads_active) || _threads.size () == 0) {
for (TaskList::iterator i = _tasklist.begin (); i != _tasklist.end(); ++i) {
(*i)();
}
return;
}
// }
uint32_t nt = std::min (_threads.size (), _tasklist.size ());

View File

@ -49,6 +49,7 @@
#include "pbd/convert.h"
#include "pbd/error.h"
#include "pbd/file_utils.h"
#include "pbd/i18n.h"
#include "pbd/md5.h"
#include "pbd/pthread_utils.h"
#include "pbd/search_path.h"
@ -116,7 +117,9 @@
#include "ardour/speakers.h"
#include "ardour/tempo.h"
#include "ardour/ticker.h"
#include "ardour/transport_fsm.h"
#include "ardour/transport_master.h"
#include "ardour/transport_master_manager.h"
#include "ardour/track.h"
#include "ardour/types_convert.h"
#include "ardour/user_bundle.h"
@ -129,8 +132,6 @@
#include "LuaBridge/LuaBridge.h"
#include "pbd/i18n.h"
#include <glibmm/checksum.h>
namespace ARDOUR {
@ -189,7 +190,6 @@ Session::Session (AudioEngine &eng,
, _base_sample_rate (0)
, _nominal_sample_rate (0)
, _current_sample_rate (0)
, transport_sub_state (0)
, _record_status (Disabled)
, _transport_sample (0)
, _seek_counter (0)
@ -251,6 +251,7 @@ Session::Session (AudioEngine &eng,
, lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool))
, _n_lua_scripts (0)
, _butler (new Butler (*this))
, _transport_fsm (TransportFSM::create (*this))
, _post_transport_work (0)
, _locations (new Locations (*this))
, _ignore_skips_updates (false)
@ -606,9 +607,13 @@ Session::immediately_post_engine ()
_process_graph.reset (new Graph (*this));
}
/* every time we reconnect, recompute worst case output latencies */
/* Restart transport FSM */
_engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this));
_transport_fsm->backend()->start ();
/* every time we reconnect, do stuff ... */
_engine.Running.connect_same_thread (*this, boost::bind (&Session::engine_running, this));
if (synced_to_engine()) {
_engine.transport_stop ();
@ -848,7 +853,6 @@ Session::destroy ()
case SessionEvent::Skip:
case SessionEvent::PunchIn:
case SessionEvent::PunchOut:
case SessionEvent::StopOnce:
case SessionEvent::RangeStop:
case SessionEvent::RangeLocate:
remove = false;
@ -880,6 +884,9 @@ Session::destroy ()
delete _selection;
_selection = 0;
_transport_fsm->backend()->stop ();
_transport_fsm.reset ();
DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n");
#ifndef NDEBUG
@ -1584,6 +1591,7 @@ Session::hookup_io ()
*/
AudioEngine::instance()->reconnect_ports ();
TransportMasterManager::instance().reconnect_ports ();
/* Anyone who cares about input state, wake up and do something */
@ -2015,7 +2023,7 @@ void
Session::_locations_changed (const Locations::LocationList& locations)
{
/* There was some mass-change in the Locations object.
*
*
* We might be re-adding a location here but it doesn't actually matter
* for all the locations that the Session takes an interest in.
*/

View File

@ -25,6 +25,7 @@
#include "pbd/stacktrace.h"
#include "ardour/butler.h"
#include "ardour/disk_reader.h"
#include "ardour/route.h"
#include "ardour/session.h"
#include "ardour/session_event.h"
@ -61,6 +62,7 @@ void
Session::schedule_playback_buffering_adjustment ()
{
add_post_transport_work (PostTransportAdjustPlaybackBuffering);
DiskReader::inc_no_disk_output ();
_butler->schedule_transport_work ();
}
@ -108,6 +110,7 @@ Session::overwrite_some_buffers (Track* t)
}
add_post_transport_work (PostTransportOverWrite);
_butler->schedule_transport_work ();
}

View File

@ -225,7 +225,6 @@ SessionEventManager::merge_event (SessionEvent* ev)
switch (ev->type) {
case SessionEvent::AutoLoop:
case SessionEvent::StopOnce:
_clear_event_type (ev->type);
break;
default:

View File

@ -95,7 +95,7 @@ Session::pre_export ()
_exporting = true;
export_status->set_running (true);
export_status->Finished.connect_same_thread (*this, boost::bind (&Session::finalize_audio_export, this));
export_status->Finished.connect_same_thread (*this, boost::bind (&Session::finalize_audio_export, this, _1));
/* disable MMC output early */
@ -153,7 +153,7 @@ Session::start_audio_export (samplepos_t position, bool realtime, bool region_ex
}
}
/* we just did the core part of a locate() call above, but
/* we just did the core part of a locate call above, but
for the sake of any GUI, put the _transport_sample in
the right place too.
*/
@ -256,7 +256,7 @@ Session::process_export_fw (pframes_t nframes)
set_transport_speed (1.0, 0, false);
butler_transport_work ();
g_atomic_int_set (&_butler->should_do_transport_work, 0);
post_transport ();
butler_completed_transport_work ();
return;
}
@ -299,7 +299,7 @@ int
Session::stop_audio_export ()
{
/* can't use stop_transport() here because we need
an immediate halt and don't require all the declick
an synchronous halt and don't require all the declick
stuff that stop_transport() implements.
*/
@ -311,8 +311,14 @@ Session::stop_audio_export ()
}
void
Session::finalize_audio_export ()
Session::finalize_audio_export (TransportRequestSource trs)
{
/* This is called as a handler for the Finished signal, which is
emitted by a UI component once the ExportStatus object associated
with this export indicates that it has finished. It runs in the UI
thread that emits the signal.
*/
_exporting = false;
if (_export_rolling) {
@ -340,6 +346,6 @@ Session::finalize_audio_export ()
if (post_export_sync) {
config.set_external_sync (true);
} else {
locate (post_export_position, false, false, false, false, false);
request_locate (post_export_position, false, trs);
}
}

View File

@ -26,9 +26,9 @@
#include <algorithm>
#include <unistd.h>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/algorithm/string/erase.hpp>
#include "pbd/i18n.h"
#include "pbd/error.h"
#include "pbd/enumwriter.h"
@ -45,6 +45,7 @@
#include "ardour/process_thread.h"
#include "ardour/scene_changer.h"
#include "ardour/session.h"
#include "ardour/transport_fsm.h"
#include "ardour/transport_master.h"
#include "ardour/transport_master_manager.h"
#include "ardour/ticker.h"
@ -54,95 +55,11 @@
#include "midi++/mmc.h"
#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace PBD;
using namespace std;
/* state machine */
namespace msm = boost::msm;
namespace mpl = boost::mpl;
namespace TransportState
{
/* events */
struct play {};
struct stop {};
/* front-end: define the FSM structure */
struct TransportFSM : public msm::front::state_machine_def<TransportFSM>
{
/* FSM states */
struct Stopped : public msm::front::state<>
{
template <class Event,class FSM> void
on_entry (Event const&, FSM&)
{
std::cout << "entering: Stopped" << std::endl;
}
template <class Event,class FSM> void
on_exit (Event const&, FSM&)
{
std::cout << "leaving: Stopped" << std::endl;
}
};
struct Playing : public msm::front::state<>
{
template <class Event,class FSM> void
on_entry (Event const&, FSM&)
{
std::cout << "entering: Playing" << std::endl;
}
template <class Event,class FSM> void
on_exit (Event const&, FSM&)
{
std::cout << "leaving: Playing" << std::endl;
}
};
/* the initial state */
typedef Stopped initial_state;
/* transition actions */
void start_playback (play const&)
{
std::cout << "player::start_playback\n";
}
void stop_playback (stop const&)
{
std::cout << "player::stop_playback\n";
}
typedef TransportFSM _t; // makes transition table cleaner
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +---------+-------------+---------+---------------------+----------------------+
a_row < Stopped , play , Playing , &_t::start_playback >,
_row < Stopped , stop , Stopped >,
// +---------+-------------+---------+---------------------+----------------------+
a_row < Playing , stop , Stopped , &_t::stop_playback >
// +---------+-------------+---------+---------------------+----------------------+
> {};
};
typedef msm::back::state_machine<TransportFSM> transport_fsm;
void test()
{
transport_fsm t;
t.start ();
t.process_event (play());
t.process_event (stop());
t.stop();
}
};
#define TFSM_EVENT(ev) { DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("TFSM(%1)\n", typeid(ev).name())); _transport_fsm->enqueue (ev); }
/** Called by the audio engine when there is work to be done with JACK.
* @param nframes Number of samples to process.
@ -161,8 +78,12 @@ Session::process (pframes_t nframes)
}
if (non_realtime_work_pending()) {
DEBUG_TRACE (DEBUG::Butler, string_compose ("non-realtime work pending: %1\n", enum_2_string (post_transport_work())));
if (!_butler->transport_work_requested ()) {
post_transport ();
DEBUG_TRACE (DEBUG::Butler, string_compose ("done, waiting? %1\n", _transport_fsm->waiting_for_butler()));
butler_completed_transport_work ();
} else {
DEBUG_TRACE (DEBUG::Butler, "not done yet\n");
}
}
@ -177,11 +98,16 @@ Session::process (pframes_t nframes)
* callig it hold a _processor_lock reader-lock
*/
boost::shared_ptr<RouteList> r = routes.reader ();
bool one_or_more_routes_declicking = false;
for (RouteList::const_iterator i = r->begin(); i != r->end(); ++i) {
if ((*i)->apply_processor_changes_rt()) {
_rt_emit_pending = true;
}
if ((*i)->declick_in_progress()) {
one_or_more_routes_declicking = true;
}
}
if (_rt_emit_pending) {
if (!_rt_thread_active) {
emit_route_signals ();
@ -193,6 +119,17 @@ Session::process (pframes_t nframes)
}
}
/* We are checking two things here:
*
* 1) whether or not all tracks have finished a declick out.
* 2) is the transport FSM waiting to be told this
*/
if (!one_or_more_routes_declicking && declick_in_progress()) {
/* end of the declick has been reached by all routes */
TFSM_EVENT (TransportFSM::declick_done());
}
_engine.main_thread()->drop_buffers ();
/* deliver MIDI clock. Note that we need to use the transport sample
@ -241,6 +178,8 @@ Session::no_roll (pframes_t nframes)
(*i)->automation_run (_transport_sample, nframes);
}
_global_locate_pending = locate_pending ();
if (_process_graph) {
DEBUG_TRACE(DEBUG::ProcessThreads,"calling graph/no-roll\n");
_process_graph->routes_no_roll( nframes, _transport_sample, end_sample, non_realtime_work_pending());
@ -281,7 +220,7 @@ Session::process_routes (pframes_t nframes, bool& need_butler)
(*i)->automation_run (start_sample, nframes);
}
_global_locate_pending = locate_pending ();
_global_locate_pending = locate_pending();
if (_process_graph) {
DEBUG_TRACE(DEBUG::ProcessThreads,"calling graph/process-routes\n");
@ -302,7 +241,7 @@ Session::process_routes (pframes_t nframes, bool& need_butler)
bool b = false;
if ((ret = (*i)->roll (nframes, start_sample, end_sample, b)) < 0) {
stop_transport ();
TFSM_EVENT (TransportFSM::stop_transport (false, false));
return -1;
}
@ -404,7 +343,7 @@ 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));
// 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;
@ -615,10 +554,8 @@ Session::process_with_events (pframes_t nframes)
if (samples_moved < 0) {
decrement_transport_position (-samples_moved);
DEBUG_TRACE (DEBUG::Transport, string_compose ("DEcrement transport by %1 to %2\n", samples_moved, _transport_sample));
} else if (samples_moved) {
increment_transport_position (samples_moved);
DEBUG_TRACE (DEBUG::Transport, string_compose ("INcrement transport by %1 to %2\n", samples_moved, _transport_sample));
} else {
DEBUG_TRACE (DEBUG::Transport, "no transport motion\n");
}
@ -699,7 +636,6 @@ Session::process_without_events (pframes_t nframes)
return;
} else {
samples_moved = (samplecnt_t) nframes * _transport_speed;
DEBUG_TRACE (DEBUG::Transport, string_compose ("no-events, plan to move transport by %1 (%2 @ %3)\n", samples_moved, nframes, _transport_speed));
}
if (!_exporting && !timecode_transmission_suspended()) {
@ -730,10 +666,10 @@ Session::process_without_events (pframes_t nframes)
if (samples_moved < 0) {
decrement_transport_position (-samples_moved);
DEBUG_TRACE (DEBUG::Transport, string_compose ("DEcrement transport by %1 to %2\n", samples_moved, _transport_sample));
//DEBUG_TRACE (DEBUG::Transport, string_compose ("DEcrement transport by %1 to %2\n", samples_moved, _transport_sample));
} else if (samples_moved) {
increment_transport_position (samples_moved);
DEBUG_TRACE (DEBUG::Transport, string_compose ("INcrement transport by %1 to %2\n", samples_moved, _transport_sample));
//DEBUG_TRACE (DEBUG::Transport, string_compose ("INcrement transport by %1 to %2\n", samples_moved, _transport_sample));
} else {
DEBUG_TRACE (DEBUG::Transport, "no transport motion\n");
}
@ -915,37 +851,27 @@ Session::process_event (SessionEvent* ev)
/* roll after locate, do not flush, set "with loop"
true only if we are seamless looping
*/
start_locate (ev->target_sample, true, false, Config->get_seamless_loop());
TFSM_EVENT (TransportFSM::locate (ev->target_sample, true, false, Config->get_seamless_loop(), false));
}
remove = false;
del = false;
break;
case SessionEvent::Locate:
if (ev->yes_or_no) { /* force locate */
/* args: do not roll after locate, do flush, not with loop */
locate (ev->target_sample, false, true, false);
} else {
/* args: do not roll after locate, do flush, not with loop */
start_locate (ev->target_sample, false, true, false);
}
/* args: do not roll after locate, do flush, not with loop, force */
TFSM_EVENT (TransportFSM::locate (ev->target_sample, false, true, false, ev->yes_or_no));
_send_timecode_update = true;
break;
case SessionEvent::LocateRoll:
if (ev->yes_or_no) {
/* args: roll after locate, do flush, not with loop */
locate (ev->target_sample, true, true, false);
} else {
/* args: roll after locate, do flush, not with loop */
start_locate (ev->target_sample, true, true, false);
}
/* args: roll after locate, do flush, not with loop, force */
TFSM_EVENT (TransportFSM::locate (ev->target_sample, true, true, false, ev->yes_or_no));
_send_timecode_update = true;
break;
case SessionEvent::Skip:
if (Config->get_skip_playback()) {
start_locate (ev->target_sample, true, true, false);
TFSM_EVENT (TransportFSM::locate (ev->target_sample, true, true, false, false));
_send_timecode_update = true;
}
remove = false;
@ -985,26 +911,15 @@ Session::process_event (SessionEvent* ev)
del = false;
break;
case SessionEvent::StopOnce:
if (!non_realtime_work_pending()) {
_clear_event_type (SessionEvent::StopOnce);
stop_transport (ev->yes_or_no);
}
remove = false;
del = false;
break;
case SessionEvent::RangeStop:
if (!non_realtime_work_pending()) {
stop_transport (ev->yes_or_no);
}
TFSM_EVENT (TransportFSM::stop_transport (ev->yes_or_no, false));
remove = false;
del = false;
break;
case SessionEvent::RangeLocate:
/* args: roll after locate, do flush, not with loop */
start_locate (ev->target_sample, true, true, false);
TFSM_EVENT (TransportFSM::locate (ev->target_sample, true, true, false, false));
remove = false;
del = false;
break;
@ -1179,35 +1094,53 @@ Session::follow_transport_master (pframes_t nframes)
slave_speed = tmm.get_current_speed_in_process_context();
slave_transport_sample = tmm.get_current_position_in_process_context ();
track_transport_master (slave_speed, slave_transport_sample);
/* transport sample may have been moved during ::track_transport_master() */
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()));
if (transport_master_tracking_state == Running) {
/* This is a heuristic rather than a strictly provable rule. The idea
* is that if we're "far away" from the master, we should locate to its
* current position, and then varispeed to sync with it.
*
* On the other hand, if we're close to it, just varispeed.
*/
if (!actively_recording() && abs (delta) > tmm.current()->resolution()) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("current 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 (!actively_recording() && abs (delta) > (5 * current_block_size)) {
DiskReader::inc_no_disk_output ();
if (!_transport_fsm->locating()) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("request locate to master position %1\n", slave_transport_sample));
TFSM_EVENT (TransportFSM::locate (slave_transport_sample, true, true, false, false));
}
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;
if (slave_speed != 0.0) {
if (_transport_speed == 0.0f) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave starts transport: %1 sample %2 tf %3\n", slave_speed, slave_transport_sample, _transport_sample));
TFSM_EVENT (TransportFSM::start_transport ());
}
} else {
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));
TFSM_EVENT (TransportFSM::stop_transport (false, false));
}
}
/* This is the second part of the "we're not synced yet" code. If we're
* close, but not within the resolution of the master, silence disk
* output but continue to varispeed to get in sync.
*/
if (!actively_recording() && abs (delta) > tmm.current()->resolution()) {
/* just varispeed to chase the master, and be silent till we're synced */
DiskReader::inc_no_disk_output ();
return true;
}
/* speed is set, we're locked, and good to go */
DiskReader::dec_no_disk_output ();
return true;
noroll:
/* don't move at all */
DEBUG_TRACE (DEBUG::Slave, "no roll\n")
@ -1215,81 +1148,9 @@ Session::follow_transport_master (pframes_t 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:
master_wait_end = slave_transport_sample + worst_latency_preroll() + master->seekahead_distance ();
DEBUG_TRACE (DEBUG::Slave, string_compose ("slave stopped, but running, requires seekahead to %1, now WAITING\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;
case Waiting:
default:
break;
}
if (transport_master_tracking_state == Waiting) {
DEBUG_TRACE (DEBUG::Slave, string_compose ("master currently at %1, waiting to pass %2\n", slave_transport_sample, master_wait_end));
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);
DiskReader::dec_no_disk_output ();
}

View File

@ -4123,7 +4123,6 @@ Session::config_changed (std::string p, bool ours)
first_file_data_format_reset = false;
} else if (p == "external-sync") {
std::cerr << "param change, rss to " << TransportMasterManager::instance().current() << std::endl;
request_sync_source (TransportMasterManager::instance().current());
} else if (p == "denormal-model") {
setup_fpu ();

View File

@ -35,6 +35,7 @@
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "ardour/transport_fsm.h"
#include "pbd/i18n.h"
@ -42,6 +43,8 @@ using namespace std;
using namespace ARDOUR;
using namespace PBD;
#define TFSM_EVENT(ev) { DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("TFSM(%1)\n", typeid(ev).name())); _transport_fsm->enqueue (ev); }
/* BBT TIME*/
void
@ -214,7 +217,7 @@ Session::backend_sync_callback (TransportState state, samplepos_t pos)
case TransportRolling:
// cerr << "SYNC: rolling slave = " << slave << endl;
if (slave) {
start_transport ();
TFSM_EVENT (TransportFSM::start_transport());
}
break;

View File

@ -32,12 +32,15 @@
#include <cerrno>
#include <unistd.h>
#include "pbd/undo.h"
#include <boost/algorithm/string/erase.hpp>
#include "pbd/error.h"
#include "pbd/enumwriter.h"
#include "pbd/pthread_utils.h"
#include "pbd/i18n.h"
#include "pbd/memento_command.h"
#include "pbd/pthread_utils.h"
#include "pbd/stacktrace.h"
#include "pbd/undo.h"
#include "midi++/mmc.h"
#include "midi++/port.h"
@ -54,6 +57,7 @@
#include "ardour/profile.h"
#include "ardour/scene_changer.h"
#include "ardour/session.h"
#include "ardour/transport_fsm.h"
#include "ardour/transport_master.h"
#include "ardour/transport_master_manager.h"
#include "ardour/tempo.h"
@ -61,8 +65,6 @@
#include "ardour/vca.h"
#include "ardour/vca_manager.h"
#include "pbd/i18n.h"
using namespace std;
using namespace ARDOUR;
using namespace PBD;
@ -80,8 +82,10 @@ using namespace PBD;
#endif
#define TFSM_EVENT(ev) { DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("TFSM(%1)\n", typeid(ev).name())); _transport_fsm->enqueue (ev); }
/* *****************************************************************************
* REALTIME ACTIONS (to be called on state transtion
* REALTIME ACTIONS (to be called on state transitions)
* ****************************************************************************/
void
@ -89,14 +93,13 @@ Session::realtime_stop (bool abort, bool clear_state)
{
ENSURE_PROCESS_THREAD;
DEBUG_TRACE (DEBUG::Transport, string_compose ("realtime stop @ %1\n", _transport_sample));
DEBUG_TRACE (DEBUG::Transport, string_compose ("realtime stop @ %1 speed = %2\n", _transport_sample, _transport_speed));
PostTransportWork todo = PostTransportWork (0);
/* assume that when we start, we'll be moving forwards */
if (_transport_speed < 0.0f) {
if (_last_transport_speed < 0.0f) {
todo = (PostTransportWork (todo | PostTransportStop | PostTransportReverse));
_default_transport_speed = 1.0;
DiskReader::inc_no_disk_output (); // for the buffer reversal
} else {
todo = PostTransportWork (todo | PostTransportStop);
}
@ -127,7 +130,6 @@ Session::realtime_stop (bool abort, bool clear_state)
add_post_transport_work (todo);
}
_clear_event_type (SessionEvent::StopOnce);
_clear_event_type (SessionEvent::RangeStop);
_clear_event_type (SessionEvent::RangeLocate);
@ -156,22 +158,13 @@ Session::realtime_stop (bool abort, bool clear_state)
waiting_for_sync_offset = true;
}
transport_sub_state = 0;
}
void
Session::realtime_locate ()
{
ENSURE_PROCESS_THREAD;
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
(*i)->realtime_locate ();
if (todo) {
TFSM_EVENT (TransportFSM::butler_required());
}
}
void
Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flush, bool for_loop_enabled, bool force)
Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, bool for_loop_enabled, bool force, bool with_mmc)
{
ENSURE_PROCESS_THREAD;
@ -195,7 +188,7 @@ Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flus
will use the incorrect _transport_sample and report an old
and incorrect time to Jack transport
*/
locate (target_sample, with_roll, with_flush, for_loop_enabled, force);
do_locate (target_sample, with_roll, with_flush, for_loop_enabled, force, with_mmc);
}
/* tell JACK to change transport position, and we will
@ -211,13 +204,13 @@ Session::start_locate (samplepos_t target_sample, bool with_roll, bool with_flus
}
} else {
locate (target_sample, with_roll, with_flush, for_loop_enabled, force);
do_locate (target_sample, with_roll, with_flush, for_loop_enabled, force, with_mmc);
}
}
/** @param with_mmc true to send a MMC locate command when the locate is done */
void
Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, bool for_loop_enabled, bool force, bool with_mmc)
Session::do_locate (samplepos_t target_sample, bool with_roll, bool with_flush, bool for_loop_enabled, bool force, bool with_mmc)
{
ENSURE_PROCESS_THREAD;
@ -246,12 +239,11 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
set_transport_speed (1.0, 0, false);
}
loop_changing = false;
TFSM_EVENT (TransportFSM::locate_done());
Located (); /* EMIT SIGNAL */
return;
}
cerr << "... now doing the actual locate to " << target_sample << " from " << _transport_sample << endl;
// Update Timecode time
_transport_sample = target_sample;
// Bump seek counter so that any in-process locate in the butler
@ -266,19 +258,30 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
* we are rolling AND
* no autoplay in effect AND
* we're not going to keep rolling after the locate AND
* !(playing a loop with JACK sync)
* !(playing a loop with JACK sync) AND
* we're not synced to an external transport master
*
*/
bool transport_was_stopped = !transport_rolling();
if (!transport_was_stopped && (!auto_play_legal || !config.get_auto_play()) && !with_roll && !(synced_to_engine() && play_loop) &&
if (!transport_was_stopped &&
(!auto_play_legal || !config.get_auto_play()) &&
!with_roll &&
!(synced_to_engine() && play_loop) &&
(!Profile->get_trx() || !(config.get_external_sync() && !synced_to_engine()))) {
realtime_stop (false, true); // XXX paul - check if the 2nd arg is really correct
transport_was_stopped = true;
} else {
/* otherwise tell the world that we located */
realtime_locate ();
/* Tell all routes to do the RT part of locate */
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
(*i)->realtime_locate ();
}
}
if (force || !for_loop_enabled || loop_changing) {
@ -379,7 +382,9 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
}
if (need_butler) {
_butler->schedule_transport_work ();
TFSM_EVENT (TransportFSM::butler_required());
} else {
TFSM_EVENT (TransportFSM::locate_done());
}
loop_changing = false;
@ -396,6 +401,17 @@ Session::locate (samplepos_t target_sample, bool with_roll, bool with_flush, boo
}
}
void
Session::post_locate ()
{
if (transport_master_is_external() && !synced_to_engine()) {
const samplepos_t current_master_position = TransportMasterManager::instance().get_current_position_in_process_context();
if (abs (current_master_position - _transport_sample) > TransportMasterManager::instance().current()->resolution()) {
_last_roll_location = _last_roll_or_reversal_location = _transport_sample;
}
}
}
/** Set the transport speed.
* Called from the process thread.
* @param speed New speed
@ -468,7 +484,7 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
_requested_return_sample = destination_sample;
}
stop_transport (abort);
TFSM_EVENT (TransportFSM::stop_transport (abort, false));
}
} else if (transport_stopped() && speed == 1.0) {
@ -505,7 +521,7 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
_engine.transport_start ();
_count_in_once = false;
} else {
start_transport ();
TFSM_EVENT (TransportFSM::start_transport());
}
} else {
@ -547,6 +563,7 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
if ((_transport_speed && speed * _transport_speed < 0.0) || (_last_transport_speed * speed < 0.0) || (_last_transport_speed == 0.0 && speed < 0.0)) {
todo = PostTransportWork (todo | PostTransportReverse);
DiskReader::inc_no_disk_output (); // for the buffer reversal
_last_roll_or_reversal_location = _transport_sample;
}
@ -559,7 +576,7 @@ Session::set_transport_speed (double speed, samplepos_t destination_sample, bool
if (todo) {
add_post_transport_work (todo);
_butler->schedule_transport_work ();
TFSM_EVENT (TransportFSM::butler_required());
}
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC3 with speed = %1\n", _transport_speed));
@ -595,14 +612,10 @@ Session::stop_transport (bool abort, bool clear_state)
ENSURE_PROCESS_THREAD;
_count_in_once = false;
if (_transport_speed == 0.0f) {
return;
}
DEBUG_TRACE (DEBUG::Transport, "time to actually stop\n");
DEBUG_TRACE (DEBUG::Transport, string_compose ("time to actually stop with TS @ %1\n", _transport_sample));
realtime_stop (abort, clear_state);
_butler->schedule_transport_work ();
}
/** Called from the process thread */
@ -705,15 +718,26 @@ Session::start_transport ()
TransportStateChange (); /* EMIT SIGNAL */
}
bool
Session::should_roll_after_locate () const
{
/* a locate must previously have been requested and completed */
return ((!config.get_external_sync() && (auto_play_legal && config.get_auto_play())) && !_exporting) || (post_transport_work() & PostTransportRoll);
}
/** Do any transport work in the audio thread that needs to be done after the
* transport thread is finished. Audio thread, realtime safe.
* butler thread is finished. Audio thread, realtime safe.
*/
void
Session::post_transport ()
Session::butler_completed_transport_work ()
{
ENSURE_PROCESS_THREAD;
PostTransportWork ptw = post_transport_work ();
DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler done, RT cleanup for %1\n", enum_2_string (ptw)));
if (ptw & PostTransportAudition) {
if (auditioner && auditioner->auditioning()) {
process_function = &Session::process_audition;
@ -722,19 +746,14 @@ Session::post_transport ()
}
}
if (ptw & PostTransportStop) {
transport_sub_state = 0;
if (ptw & PostTransportLocate) {
post_locate ();
TFSM_EVENT (TransportFSM::locate_done());
}
if (ptw & PostTransportLocate) {
if (((!config.get_external_sync() && (auto_play_legal && config.get_auto_play())) && !_exporting) || (ptw & PostTransportRoll)) {
_count_in_once = false;
start_transport ();
} else {
transport_sub_state = 0;
}
if (ptw & PostTransportAdjustPlaybackBuffering) {
/* we blocked output while this happened */
DiskReader::dec_no_disk_output ();
}
set_next_event ();
@ -742,6 +761,20 @@ Session::post_transport ()
know were handled ?
*/
set_post_transport_work (PostTransportWork (0));
if (_transport_fsm->waiting_for_butler()) {
TFSM_EVENT (TransportFSM::butler_done());
}
DiskReader::dec_no_disk_output ();
}
void
Session::schedule_butler_for_transport_work ()
{
assert (_transport_fsm->waiting_for_butler ());
DEBUG_TRACE (DEBUG::Butler, "summon butler for transport work\n");
_butler->schedule_transport_work ();
}
bool
@ -752,7 +785,7 @@ Session::maybe_stop (samplepos_t limit)
if (synced_to_engine () && config.get_jack_time_master ()) {
_engine.transport_stop ();
} else if (!synced_to_engine ()) {
stop_transport ();
TFSM_EVENT (TransportFSM::stop_transport ());
}
return true;
}
@ -861,11 +894,11 @@ Session::set_play_loop (bool yn, double speed)
rolling, do not locate to loop start.
*/
if (!transport_rolling() && (speed != 0.0)) {
start_locate (loc->start(), true, true, false, true);
TFSM_EVENT (TransportFSM::locate (loc->start(), true, true, false, true));
}
} else {
if (speed != 0.0) {
start_locate (loc->start(), true, true, false, true);
TFSM_EVENT (TransportFSM::locate (loc->start(), true, true, false, true));
}
}
}
@ -1114,14 +1147,14 @@ Session::request_cancel_play_range ()
bool
Session::solo_selection_active ()
{
if ( _soloSelection.empty() ) {
if (_soloSelection.empty()) {
return false;
}
return true;
}
void
Session::solo_selection ( StripableList &list, bool new_state )
Session::solo_selection (StripableList &list, bool new_state)
{
boost::shared_ptr<ControlList> solo_list (new ControlList);
boost::shared_ptr<ControlList> unsolo_list (new ControlList);
@ -1176,7 +1209,7 @@ Session::butler_transport_work ()
PostTransportWork ptw = post_transport_work();
uint64_t before;
DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler transport work, todo = %1 at %2\n", enum_2_string (ptw), (before = g_get_monotonic_time())));
DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler transport work, todo = [%1] (0x%3%4%5) at %2\n", enum_2_string (ptw), (before = g_get_monotonic_time()), std::hex, ptw, std::dec));
if (ptw & PostTransportLocate) {
@ -1311,18 +1344,6 @@ Session::non_realtime_overwrite (int on_entry, bool& finished)
}
}
bool
Session::declick_in_progress () const
{
boost::shared_ptr<RouteList> rl = routes.reader();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
if ((*i)->declick_in_progress ()) {
return true;
}
}
return false;
}
void
Session::non_realtime_locate ()
{
@ -1370,6 +1391,7 @@ Session::non_realtime_locate ()
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
(*i)->non_realtime_locate (tf);
if (sc != g_atomic_int_get (&_seek_counter)) {
std::cerr << "\n\nLOCATE INTERRUPTED BY LOCATE!!!\n\n";
goto restart;
}
}
@ -1757,7 +1779,7 @@ Session::unset_play_loop ()
if (Config->get_seamless_loop()) {
/* likely need to flush track buffers: this will locate us to wherever we are */
add_post_transport_work (PostTransportLocate);
_butler->schedule_transport_work ();
TFSM_EVENT (TransportFSM::butler_required());
}
TransportStateChange (); /* EMIT SIGNAL */
}
@ -1901,27 +1923,37 @@ Session::request_roll_at_and_return (samplepos_t start, samplepos_t return_to)
void
Session::engine_halted ()
{
bool ignored;
/* there will be no more calls to process(), so
we'd better clean up for ourselves, right now.
but first, make sure the butler is out of
the picture.
We can't queue SessionEvents because they only get
handled from within a process callback.
*/
if (_butler) {
_butler->stop ();
}
/* this just stops the FSM engine ... it doesn't change the state of
* the FSM directly or anything else ... but the FSM will be
* reinitialized when we call its ::start() method from
* ::engine_running() (if we ever get there)
*/
_transport_fsm->backend()->stop ();
/* Synchronously do the realtime part of a transport stop.
*
* Calling this will cause the butler to asynchronously run
* ::non_realtime_stop() where the rest of the "stop" work will be
* done.
*/
realtime_stop (false, true);
non_realtime_stop (false, 0, ignored);
transport_sub_state = 0;
DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC6 with speed = %1\n", _transport_speed));
TransportStateChange (); /* EMIT SIGNAL */
}
void
Session::engine_running ()
{
initialize_latencies ();
_transport_fsm->backend()->start ();
}
void
Session::xrun_recovery ()
@ -2054,7 +2086,7 @@ Session::sync_source_changed (SyncSource type, samplepos_t pos, pframes_t cycle_
longer valid with a new slave.
*/
DiskReader::set_no_disk_output (false);
DiskReader::dec_no_disk_output ();
#if 0
we should not be treating specific transport masters as special cases because there maybe > 1 of a particular type
@ -2101,3 +2133,27 @@ Session::sync_source_changed (SyncSource type, samplepos_t pos, pframes_t cycle_
set_dirty();
}
bool
Session::transport_stopped() const
{
return _transport_fsm->stopped();
}
bool
Session::transport_rolling() const
{
return _transport_speed != 0.0 && _count_in_samples == 0 && _remaining_latency_preroll == 0;
}
bool
Session::locate_pending () const
{
return _transport_fsm->locating();
}
bool
Session::declick_in_progress () const
{
return _transport_fsm->declick_in_progress();
}

View File

@ -264,7 +264,7 @@ Track::freeze_state() const
bool
Track::declick_in_progress () const
{
return _disk_reader->declick_in_progress ();
return active() && _disk_reader->declick_in_progress ();
}
bool

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2019 Robin Gareus <robin@gareus.org>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "ardour/debug.h"
#include "ardour/session.h"
#include "ardour/transport_fsm.h"
using namespace ARDOUR;
using namespace PBD;
/* transition actions */
void
TransportFSM::start_playback (TransportFSM::start_transport const& p)
{
DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::start_playback\n");
api->start_transport();
}
void
TransportFSM::start_declick (TransportFSM::stop_transport const &s)
{
DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::start_declick\n");
_last_stop = s;
}
void
TransportFSM::stop_playback (TransportFSM::declick_done const& /*ignored*/)
{
DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::stop_playback\n");
api->stop_transport (_last_stop.abort, _last_stop.clear_state);
}
void
TransportFSM::save_locate_and_start_declick (TransportFSM::locate const & l)
{
DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::save_locate_and_stop\n");
_last_locate = l;
start_declick (stop_transport (false, false));
}
void
TransportFSM::start_locate (TransportFSM::locate const& l)
{
DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::start_locate\n");
api->locate (l.target, l.with_roll, l.with_flush, l.with_loop, l.force);
}
void
TransportFSM::start_saved_locate (TransportFSM::declick_done const&)
{
DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::start_save\n");
api->locate (_last_locate.target, _last_locate.with_roll, _last_locate.with_flush, _last_locate.with_loop, _last_locate.force);
}
void
TransportFSM::interrupt_locate (TransportFSM::locate const& l)
{
DEBUG_TRACE (DEBUG::TFSMEvents, "tfsm::interrupt\n");
/* maintain original "with-roll" choice of initial locate, even though
* we are interrupting the locate to start a new one.
*/
api->locate (l.target, _last_locate.with_roll, l.with_flush, l.with_loop, l.force);
}
void
TransportFSM::schedule_butler_for_transport_work (TransportFSM::butler_required const&)
{
api->schedule_butler_for_transport_work ();
}
bool
TransportFSM::should_roll_after_locate (TransportFSM::locate_done const &)
{
bool ret = api->should_roll_after_locate ();
DEBUG_TRACE (DEBUG::TFSMEvents, string_compose ("tfsm::should_roll_after_locate() ? %1\n", ret));
return ret;
}
void
TransportFSM::roll_after_locate (TransportFSM::locate_done const &)
{
DEBUG_TRACE (DEBUG::TFSMEvents, "rolling after locate\n");
api->start_transport ();
}

View File

@ -74,6 +74,7 @@ TransportMaster::TransportMaster (SyncSource t, std::string const & name)
, _sclock_synced (Properties::sclock_synced, false)
, _collect (Properties::collect, true)
, _connected (Properties::connected, false)
, port_node (X_(""))
{
register_properties ();
@ -260,7 +261,27 @@ TransportMaster::set_state (XMLNode const & node, int /* version */)
XMLNode* pnode = node.child (X_("Port"));
if (pnode) {
XMLNodeList const & children = pnode->children();
port_node = *pnode;
if (AudioEngine::instance()->running()) {
connect_port_using_state ();
}
}
PropertyChanged (what_changed);
return 0;
}
void
TransportMaster::connect_port_using_state ()
{
if (!_port) {
create_port ();
}
if (_port) {
XMLNodeList const & children = port_node.children();
for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
XMLProperty const *prop;
@ -273,10 +294,6 @@ TransportMaster::set_state (XMLNode const & node, int /* version */)
}
}
}
PropertyChanged (what_changed);
return 0;
}
XMLNode&
@ -360,21 +377,27 @@ TransportMaster::factory (SyncSource type, std::string const& name, bool removea
DEBUG_TRACE (DEBUG::Slave, string_compose ("factory-construct %1 name %2 removeable %3\n", enum_2_string (type), name, removeable));
switch (type) {
case MTC:
tm.reset (new MTC_TransportMaster (name));
break;
case LTC:
tm.reset (new LTC_TransportMaster (name));
break;
case MIDIClock:
tm.reset (new MIDIClock_TransportMaster (name));
break;
case Engine:
tm.reset (new Engine_TransportMaster (*AudioEngine::instance()));
break;
default:
break;
try {
switch (type) {
case MTC:
tm.reset (new MTC_TransportMaster (name));
break;
case LTC:
tm.reset (new LTC_TransportMaster (name));
break;
case MIDIClock:
tm.reset (new MIDIClock_TransportMaster (name));
break;
case Engine:
tm.reset (new Engine_TransportMaster (*AudioEngine::instance()));
break;
default:
break;
}
} catch (...) {
error << string_compose (_("Construction of transport master object of type %1 failed"), enum_2_string (type)) << endmsg;
std::cerr << string_compose (_("Construction of transport master object of type %1 failed"), enum_2_string (type)) << std::endl;
return boost::shared_ptr<TransportMaster>();
}
if (tm) {
@ -444,7 +467,7 @@ 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) {
if ((p = AudioEngine::instance()->register_input_port (DataType::MIDI, port_name, false, TransportMasterPort)) == 0) {
return boost::shared_ptr<Port> ();
}
@ -490,4 +513,3 @@ TimecodeTransportMaster::set_fr2997 (bool yn)
PropertyChanged (Properties::fr2997);
}
}

View File

@ -20,16 +20,12 @@
#include "ardour/debug.h"
#include "ardour/disk_reader.h"
#include "ardour/session.h"
#include "ardour/rc_configuration.h"
#include "ardour/transport_master_manager.h"
#include "pbd/boost_debug.cc"
#include "pbd/i18n.h"
#if __cplusplus > 199711L
#define local_signbit(x) std::signbit (x)
#else
#define local_signbit(x) ((((__int64*)(&z))*) & 0x8000000000000000)
#endif
#include "pbd/stateful.h"
using namespace ARDOUR;
using namespace PBD;
@ -51,6 +47,27 @@ TransportMasterManager::~TransportMasterManager ()
clear ();
}
TransportMasterManager&
TransportMasterManager::create ()
{
assert (!_instance);
cerr << "TMM::create(), Config = " << Config << " size will be " << sizeof (TransportMasterManager) << endl;
_instance = new TransportMasterManager;
XMLNode* tmm_node = Config->extra_xml (X_("TransportMasters"));
if (tmm_node) {
cerr << " setting state via XML\n";
_instance->set_state (*tmm_node, Stateful::current_state_version);
} else {
cerr << " setting default config\n";
_instance->set_default_configuration ();
}
return *_instance;
}
int
TransportMasterManager::set_default_configuration ()
{
@ -106,7 +123,7 @@ TransportMasterManager::parameter_changed (std::string const & what)
if (what == "external-sync") {
if (!_session->config.get_external_sync()) {
/* disabled */
DiskReader::set_no_disk_output (false);
DiskReader::dec_no_disk_output ();
}
}
}
@ -115,7 +132,8 @@ TransportMasterManager&
TransportMasterManager::instance()
{
if (!_instance) {
_instance = new TransportMasterManager();
fatal << string_compose (_("programming error:%1"), X_("TransportMasterManager::instance() called without an instance!")) << endmsg;
/* NOTREACHED */
}
return *_instance;
}
@ -196,7 +214,7 @@ TransportMasterManager::pre_process_transport_masters (pframes_t nframes, sample
if (master_dll_initstate == 0) {
init_transport_master_dll (_master_speed, _master_position);
// _master_invalid_this_cycle = true;
_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;
@ -220,12 +238,12 @@ TransportMasterManager::pre_process_transport_masters (pframes_t nframes, sample
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);
DiskReader::inc_no_disk_output ();
} else {
DiskReader::set_no_disk_output (false);
DiskReader::dec_no_disk_output ();
}
} else {
DiskReader::set_no_disk_output (false);
DiskReader::dec_no_disk_output ();
}
/* inject DLL with new data */
@ -330,6 +348,11 @@ TransportMasterManager::add (SyncSource type, std::string const & name, bool rem
}
tm = TransportMaster::factory (type, name, removeable);
if (!tm) {
return -1;
}
boost_debug_shared_ptr_mark_interesting (tm.get(), "tm");
ret = add_locked (tm);
}
@ -396,6 +419,10 @@ TransportMasterManager::set_current_locked (boost::shared_ptr<TransportMaster> c
}
}
if (!c->usable()) {
return -1;
}
_current_master = c;
_master_speed = 0;
_master_position = 0;
@ -490,6 +517,7 @@ TransportMasterManager::clear ()
int
TransportMasterManager::set_state (XMLNode const & node, int version)
{
PBD::stacktrace (std::cerr, 20);
assert (node.name() == state_node_name);
XMLNodeList const & children = node.children();
@ -500,11 +528,22 @@ TransportMasterManager::set_state (XMLNode const & node, int version)
_current_master.reset ();
boost_debug_list_ptrs ();
_transport_masters.clear ();
/* TramsportMasters live for the entire life of the
* program. TransportMasterManager::set_state() should only be
* called at the start of the program, and there should be no
* transport masters at that time.
*/
assert (_transport_masters.empty());
for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
boost::shared_ptr<TransportMaster> tm = TransportMaster::factory (**c);
if (!tm) {
continue;
}
boost_debug_shared_ptr_mark_interesting (tm.get(), "tm");
if (add_locked (tm)) {
@ -518,8 +557,17 @@ TransportMasterManager::set_state (XMLNode const & node, int version)
}
std::string current_master;
if (node.get_property (X_("current"), current_master)) {
/* may fal if current_master is not usable */
set_current (current_master);
if (!current()) {
set_current (MTC); // always available
}
} else {
set_current (MTC);
}
@ -578,10 +626,14 @@ TransportMasterManager::restart ()
XMLNode* node;
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? */
Glib::Threads::RWLock::ReaderLock lm (lock);
for (TransportMasters::const_iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) {
(*tm)->connect_port_using_state ();
(*tm)->reset (false);
}
} else {
if (TransportMasterManager::instance().set_default_configuration ()) {
error << _("Cannot initialize transport master manager") << endmsg;
@ -589,3 +641,16 @@ TransportMasterManager::restart ()
}
}
}
void
TransportMasterManager::reconnect_ports ()
{
DEBUG_TRACE (DEBUG::Slave, "reconnecting all transport master ports\n");
{
Glib::Threads::RWLock::ReaderLock lm (lock);
for (TransportMasters::const_iterator tm = _transport_masters.begin(); tm != _transport_masters.end(); ++tm) {
(*tm)->connect_port_using_state ();
}
}
}

View File

@ -248,6 +248,7 @@ libardour_sources = [
'track.cc',
'transient_detector.cc',
'transform.cc',
'transport_fsm.cc',
'transport_master.cc',
'transport_master_manager.cc',
'transpose.cc',

View File

@ -57,8 +57,6 @@ void
Transmitter::deliver ()
{
string foo;
/* NOTE: this is just a default action for a Transmitter or a
derived class. Any class can override this to produce some
other action when deliver() is called.
@ -68,8 +66,7 @@ Transmitter::deliver ()
/* send the SigC++ signal */
foo = str();
(*send) (channel, foo.c_str());
(*send) (channel, str().c_str());
/* XXX when or how can we delete this ? */
// delete foo;

View File

@ -201,7 +201,7 @@ static int export_session (Session *session,
}
printf("\n");
status->finish ();
status->finish (TRS_UI);
printf ("* Done.\n");
return 0;

11
wscript
View File

@ -383,6 +383,17 @@ int main() { return 0; }''',
autowaf.set_basic_compiler_flags (conf,flags_dict)
#
# the transition table for the libardour transport state machine
# is larger than the default that is hard-coded in boost::mpl.
# These need to be defined before any boost headers are used,
# and just about the only way to be sure that is true is to define
# them on the "command line" here.
#
cxx_flags.append ('-DBOOST_MPL_CFG_NO_PREPROCESSED_HEADERS')
cxx_flags.append ('-DBOOST_MPL_LIMIT_VECTOR_SIZE=30')
cxx_flags.append ('-DBOOST_MPL_LIMIT_MAP_SIZE=30')
if conf.options.asan:
conf.check_cxx(cxxflags=["-fsanitize=address", "-fno-omit-frame-pointer"], linkflags=["-fsanitize=address"])
cxx_flags.append('-fsanitize=address')