diff --git a/gtk2_ardour/export_dialog.cc b/gtk2_ardour/export_dialog.cc index cc19965a9c..8e197060b0 100644 --- a/gtk2_ardour/export_dialog.cc +++ b/gtk2_ardour/export_dialog.cc @@ -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(); diff --git a/gtk2_ardour/export_video_dialog.cc b/gtk2_ardour/export_video_dialog.cc index 14f60d7aa5..33fd758843 100644 --- a/gtk2_ardour/export_video_dialog.cc +++ b/gtk2_ardour/export_video_dialog.cc @@ -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; diff --git a/gtk2_ardour/gui_object.h b/gtk2_ardour/gui_object.h index e77dfa9a66..2a9fb00532 100644 --- a/gtk2_ardour/gui_object.h +++ b/gtk2_ardour/gui_object.h @@ -24,8 +24,6 @@ #include #include -#include - #include "pbd/xml++.h" #include "pbd/id.h" diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h index f8cfef4009..cd96aba33a 100644 --- a/libs/ardour/ardour/debug.h +++ b/libs/ardour/ardour/debug.h @@ -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; diff --git a/libs/ardour/ardour/disk_io.h b/libs/ardour/ardour/disk_io.h index 60ac7be124..52cfe45d0e 100644 --- a/libs/ardour/ardour/disk_io.h +++ b/libs/ardour/ardour/disk_io.h @@ -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; diff --git a/libs/ardour/ardour/disk_reader.h b/libs/ardour/ardour/disk_reader.h index a7a74027f0..47812f54c8 100644 --- a/libs/ardour/ardour/disk_reader.h +++ b/libs/ardour/ardour/disk_reader.h @@ -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* sum_buffer, diff --git a/libs/ardour/ardour/export_status.h b/libs/ardour/ardour/export_status.h index 0fb4e39a78..7f36e60e06 100644 --- a/libs/ardour/ardour/export_status.h +++ b/libs/ardour/ardour/export_status.h @@ -54,8 +54,8 @@ class LIBARDOUR_API ExportStatus { } Glib::Threads::Mutex& lock () { return _run_lock; } - PBD::Signal0 Finished; - void finish (); + PBD::Signal1 Finished; + void finish (TransportRequestSource); void cleanup (); diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index d2d0b39dbd..337b88a8e8 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -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(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 _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&); diff --git a/libs/ardour/ardour/session_event.h b/libs/ardour/ardour/session_event.h index bb85624305..98fe6838f5 100644 --- a/libs/ardour/ardour/session_event.h +++ b/libs/ardour/ardour/session_event.h @@ -63,7 +63,6 @@ public: /* only one of each of these events can be queued at any one time */ - StopOnce, AutoLoop, }; diff --git a/libs/ardour/ardour/transport_api.h b/libs/ardour/ardour/transport_api.h new file mode 100644 index 0000000000..8ca7aeb074 --- /dev/null +++ b/libs/ardour/ardour/transport_api.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 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., 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 diff --git a/libs/ardour/ardour/transport_fsm.h b/libs/ardour/ardour/transport_fsm.h new file mode 100644 index 0000000000..6a920eef4d --- /dev/null +++ b/libs/ardour/ardour/transport_fsm.h @@ -0,0 +1,213 @@ +#ifndef _ardour_transport_fsm_h_ +#define _ardour_transport_fsm_h_ + +#include +#include +#include +#include +#include + +#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 +{ + /* 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 void on_entry (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "entering: " # State "\n"); } \ + template 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 void on_entry (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "entering: " # State "\n"); } \ + template void on_exit (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "leaving: " # State "\n"); } \ + typedef mpl::vector1 flag_list; \ + } + +#define define_state_flag2(State,Flag1,Flag2) \ + struct State : public msm::front::state<> \ + { \ + template void on_entry (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "entering: " # State "\n"); } \ + template void on_exit (Event const&, FSM&) { DEBUG_TRACE (PBD::DEBUG::TFSMState, "leaving: " # State "\n"); } \ + typedef mpl::vector2 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 back; + + boost::weak_ptr wp; + + bool locating () { return backend()->is_flag_active(); } + bool locating (declick_done const &) { return locating(); } + bool rolling () { return backend()->is_flag_active(); } + bool stopped () { return backend()->is_flag_active(); } + bool waiting_for_butler() { return backend()->is_flag_active(); } + bool declick_in_progress() { return backend()->is_flag_active(); } + + static boost::shared_ptr create(TransportAPI& api) { + + boost::shared_ptr p (new back ()); + + p->wp = p; + p->api = &api; + return p; + } + + boost::shared_ptr backend() { return wp.lock(); } + + template void enqueue (Event const & e) { + backend()->process_event (e); + } + + /* the initial state */ + typedef boost::mpl::vector 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 + + 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 + void no_transition(Event const& e, FSM&,int state) + { + typedef typename boost::msm::back::recursive_get_transition_table::type recursive_stt; + typedef typename boost::msm::back::generate_state_set::type all_states; + std::string stateName; + boost::mpl::for_each >(boost::msm::back::get_state_name(stateName, state)); + std::cout << "No transition from state: " << PBD::demangle (stateName) << " on event " << typeid(e).name() << std::endl; + } +}; + +} /* end namespace ARDOUR */ + +#endif diff --git a/libs/ardour/ardour/transport_master.h b/libs/ardour/ardour/transport_master.h index 3dcfe4cd8f..98c4f11156 100644 --- a/libs/ardour/ardour/transport_master.h +++ b/libs/ardour/ardour/transport_master.h @@ -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; + XMLNode port_node; + PBD::ScopedConnection port_connection; bool connection_handler (boost::weak_ptr, std::string name1, boost::weak_ptr, 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; diff --git a/libs/ardour/ardour/transport_master_manager.h b/libs/ardour/ardour/transport_master_manager.h index a10b014098..e0926f74ec 100644 --- a/libs/ardour/ardour/transport_master_manager.h +++ b/libs/ardour/ardour/transport_master_manager.h @@ -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(); diff --git a/libs/ardour/bundle.cc b/libs/ardour/bundle.cc index 8d3d52158e..b165155e90 100644 --- a/libs/ardour/bundle.cc +++ b/libs/ardour/bundle.cc @@ -478,8 +478,8 @@ Bundle::connected_to (boost::shared_ptr 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 p = engine.get_port_by_name(*j); for (Bundle::PortList::const_iterator k = other_ports.begin(); diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc index ad574af1ff..403bb03263 100644 --- a/libs/ardour/debug.cc +++ b/libs/ardour/debug.cc @@ -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"); diff --git a/libs/ardour/disk_reader.cc b/libs/ardour/disk_reader.cc index 4b13b1678a..828019f28a 100644 --- a/libs/ardour/disk_reader.cc +++ b/libs/ardour/disk_reader.cc @@ -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 (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 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 mt = boost::dynamic_pointer_cast(_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; } diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc index 5ca616001b..2665a14d94 100644 --- a/libs/ardour/enums.cc +++ b/libs/ardour/enums.cc @@ -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); diff --git a/libs/ardour/export_status.cc b/libs/ardour/export_status.cc index 9df56840a6..d278afb312 100644 --- a/libs/ardour/export_status.cc +++ b/libs/ardour/export_status.cc @@ -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 diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index f70e015b11..cbb7e4da0e 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -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 (); } } diff --git a/libs/ardour/ltc_slave.cc b/libs/ardour/ltc_slave.cc index 365839c523..f2335bba74 100644 --- a/libs/ardour/ltc_slave.cc +++ b/libs/ardour/ltc_slave.cc @@ -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) { diff --git a/libs/ardour/midi_clock_slave.cc b/libs/ardour/midi_clock_slave.cc index 5a06187ef4..cf2c84c034 100644 --- a/libs/ardour/midi_clock_slave.cc +++ b/libs/ardour/midi_clock_slave.cc @@ -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) { diff --git a/libs/ardour/mtc_slave.cc b/libs/ardour/mtc_slave.cc index 3f045e26d4..048ac95455 100644 --- a/libs/ardour/mtc_slave.cc +++ b/libs/ardour/mtc_slave.cc @@ -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) { diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc index f80ddf3915..417157f411 100644 --- a/libs/ardour/playlist.cc +++ b/libs/ardour/playlist.cc @@ -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 diff --git a/libs/ardour/port.cc b/libs/ardour/port.cc index 03210baf80..9543233710 100644 --- a/libs/ardour/port.cc +++ b/libs/ardour/port.cc @@ -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 diff --git a/libs/ardour/rt_tasklist.cc b/libs/ardour/rt_tasklist.cc index a0671968bf..9dc9b635b4 100644 --- a/libs/ardour/rt_tasklist.cc +++ b/libs/ardour/rt_tasklist.cc @@ -51,8 +51,7 @@ RTTaskList::drop_threads () for (uint32_t i = 0; i < nt; ++i) { _task_run_sem.signal (); } - for (std::vector::const_iterator i = _threads.begin (); i != _threads.end (); ++i) - { + for (std::vector::const_iterator i = _threads.begin (); i != _threads.end (); ++i) { pthread_join (*i, NULL); } _threads.clear (); @@ -122,7 +121,7 @@ RTTaskList::run () boost::function 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 ()); diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index d61a53cd52..6935150ab6 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -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 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. */ diff --git a/libs/ardour/session_butler.cc b/libs/ardour/session_butler.cc index 492346b12b..1f472b710b 100644 --- a/libs/ardour/session_butler.cc +++ b/libs/ardour/session_butler.cc @@ -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 (); } diff --git a/libs/ardour/session_events.cc b/libs/ardour/session_events.cc index e4492e15ac..db3ba85e0b 100644 --- a/libs/ardour/session_events.cc +++ b/libs/ardour/session_events.cc @@ -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: diff --git a/libs/ardour/session_export.cc b/libs/ardour/session_export.cc index 8d7b4da6c5..c1a7f5f388 100644 --- a/libs/ardour/session_export.cc +++ b/libs/ardour/session_export.cc @@ -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); } } diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index b47653c3bf..7661115463 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -26,9 +26,9 @@ #include #include -#include -#include +#include +#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 - { - - /* FSM states */ - struct Stopped : public msm::front::state<> - { - template void - on_entry (Event const&, FSM&) - { - std::cout << "entering: Stopped" << std::endl; - } - template void - on_exit (Event const&, FSM&) - { - std::cout << "leaving: Stopped" << std::endl; - } - }; - - struct Playing : public msm::front::state<> - { - template void - on_entry (Event const&, FSM&) - { - std::cout << "entering: Playing" << std::endl; - } - - template 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 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 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 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 (); } diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index e38fd57db1..1b9eeea59f 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -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 (); diff --git a/libs/ardour/session_time.cc b/libs/ardour/session_time.cc index 550a04cc04..674ea7b67b 100644 --- a/libs/ardour/session_time.cc +++ b/libs/ardour/session_time.cc @@ -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; diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 0c424704bf..3ab5c64f6e 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -32,12 +32,15 @@ #include #include -#include "pbd/undo.h" +#include + #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 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 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 solo_list (new ControlList); boost::shared_ptr 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 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(); +} diff --git a/libs/ardour/track.cc b/libs/ardour/track.cc index e80026742a..38af2d86b0 100644 --- a/libs/ardour/track.cc +++ b/libs/ardour/track.cc @@ -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 diff --git a/libs/ardour/transport_fsm.cc b/libs/ardour/transport_fsm.cc new file mode 100644 index 0000000000..ea2be0eb3f --- /dev/null +++ b/libs/ardour/transport_fsm.cc @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 Robin Gareus + * + * 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 (); +} + diff --git a/libs/ardour/transport_master.cc b/libs/ardour/transport_master.cc index 64c8062281..4d3aa82053 100644 --- a/libs/ardour/transport_master.cc +++ b/libs/ardour/transport_master.cc @@ -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(); } if (tm) { @@ -444,7 +467,7 @@ TransportMasterViaMIDI::create_midi_port (std::string const & port_name) { boost::shared_ptr p; - if ((p = AudioEngine::instance()->register_input_port (DataType::MIDI, port_name)) == 0) { + if ((p = AudioEngine::instance()->register_input_port (DataType::MIDI, port_name, false, TransportMasterPort)) == 0) { return boost::shared_ptr (); } @@ -490,4 +513,3 @@ TimecodeTransportMaster::set_fr2997 (bool yn) PropertyChanged (Properties::fr2997); } } - diff --git a/libs/ardour/transport_master_manager.cc b/libs/ardour/transport_master_manager.cc index 505dbe3576..93db2113ee 100644 --- a/libs/ardour/transport_master_manager.cc +++ b/libs/ardour/transport_master_manager.cc @@ -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 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 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 (); + } + } +} diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 9e3283a173..dbdce22361 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -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', diff --git a/libs/pbd/transmitter.cc b/libs/pbd/transmitter.cc index f1ae75deb9..719e30a122 100644 --- a/libs/pbd/transmitter.cc +++ b/libs/pbd/transmitter.cc @@ -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; diff --git a/session_utils/export.cc b/session_utils/export.cc index 15b9991fce..c6628b6db4 100644 --- a/session_utils/export.cc +++ b/session_utils/export.cc @@ -201,7 +201,7 @@ static int export_session (Session *session, } printf("\n"); - status->finish (); + status->finish (TRS_UI); printf ("* Done.\n"); return 0; diff --git a/wscript b/wscript index 1933c3af66..da86736a3d 100644 --- a/wscript +++ b/wscript @@ -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')