2008-06-02 17:41:35 -04:00
/*
2019-08-02 22:01:25 -04:00
* Copyright ( C ) 1999 - 2019 Paul Davis < paul @ linuxaudiosystems . com >
* Copyright ( C ) 2005 - 2009 Taybin Rutkin < taybin @ taybin . com >
* Copyright ( C ) 2006 - 2007 Jesse Chappell < jesse @ essej . net >
* Copyright ( C ) 2006 - 2012 David Robillard < d @ drobilla . net >
* Copyright ( C ) 2007 - 2012 Carl Hetherington < carl @ carlh . net >
* Copyright ( C ) 2008 - 2009 Hans Baier < hansfbaier @ googlemail . com >
* Copyright ( C ) 2012 - 2019 Robin Gareus < robin @ gareus . org >
* Copyright ( C ) 2014 - 2018 Ben Loftis < ben @ harrisonconsoles . com >
* Copyright ( C ) 2015 GZharun < grygoriiz @ wavesglobal . 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 .
*/
2008-06-02 17:41:35 -04:00
2011-02-15 13:47:10 -05:00
# ifdef WAF_BUILD
# include "libardour-config.h"
# endif
2012-05-24 02:09:29 -04:00
# include <cmath>
# include <cerrno>
# include <unistd.h>
2008-06-02 17:41:35 -04:00
2019-09-17 20:26:03 -04:00
# include <boost/algorithm/string/erase.hpp>
2023-02-17 02:31:21 -05:00
# include "pbd/atomic.h"
2009-02-25 13:26:51 -05:00
# include "pbd/error.h"
2009-11-30 11:12:13 -05:00
# include "pbd/enumwriter.h"
2019-09-17 20:26:03 -04:00
# include "pbd/i18n.h"
2009-02-25 13:26:51 -05:00
# include "pbd/memento_command.h"
2019-09-17 20:26:03 -04:00
# include "pbd/pthread_utils.h"
2014-04-17 09:47:06 -04:00
# include "pbd/stacktrace.h"
2019-09-17 20:26:03 -04:00
# include "pbd/undo.h"
2008-06-02 17:41:35 -04:00
2009-02-25 13:26:51 -05:00
# include "midi++/mmc.h"
# include "midi++/port.h"
2008-06-02 17:41:35 -04:00
2019-11-15 17:50:05 -05:00
# include "ardour/audio_backend.h"
2009-10-23 19:23:00 -04:00
# include "ardour/audioengine.h"
2009-02-25 13:26:51 -05:00
# include "ardour/auditioner.h"
2017-01-23 07:25:24 -05:00
# include "ardour/automation_watch.h"
2009-10-23 19:23:00 -04:00
# include "ardour/butler.h"
2011-10-18 23:34:02 -04:00
# include "ardour/click.h"
2009-11-30 11:12:13 -05:00
# include "ardour/debug.h"
2017-07-27 17:27:49 -04:00
# include "ardour/disk_reader.h"
2009-02-25 13:26:51 -05:00
# include "ardour/location.h"
2018-02-11 10:39:36 -05:00
# include "ardour/playlist.h"
2014-04-17 09:47:06 -04:00
# include "ardour/profile.h"
2014-05-02 18:43:37 -04:00
# include "ardour/scene_changer.h"
2009-10-23 19:23:00 -04:00
# include "ardour/session.h"
2021-11-10 17:55:58 -05:00
# include "ardour/tempo.h"
2019-09-17 20:26:03 -04:00
# include "ardour/transport_fsm.h"
2018-09-18 18:51:59 -04:00
# include "ardour/transport_master.h"
# include "ardour/transport_master_manager.h"
2021-11-10 17:55:58 -05:00
# include "ardour/triggerbox.h"
2011-01-19 12:38:56 -05:00
# include "ardour/operations.h"
2017-07-21 15:46:36 -04:00
# include "ardour/vca.h"
# include "ardour/vca_manager.h"
2008-06-02 17:41:35 -04:00
using namespace std ;
using namespace ARDOUR ;
using namespace PBD ;
2020-11-11 11:31:52 -05:00
using namespace Temporal ;
2019-03-13 11:26:17 -04:00
# ifdef NDEBUG
# define ENSURE_PROCESS_THREAD do {} while (0)
# else
# define ENSURE_PROCESS_THREAD \
do { \
if ( ! AudioEngine : : instance ( ) - > in_process_thread ( ) ) { \
2019-09-18 12:08:20 -04:00
PBD : : stacktrace ( std : : cerr , 30 ) ; \
2019-03-13 11:26:17 -04:00
} \
} while ( 0 )
# endif
2019-09-20 11:38:09 -04:00
# define TFSM_EVENT(evtype) { _transport_fsm->enqueue (new TransportFSM::Event (evtype)); }
2021-07-20 00:36:37 -04:00
# define TFSM_ROLL() { _transport_fsm->enqueue (new TransportFSM::Event (TransportFSM::StartTransport)); }
2019-09-20 11:38:09 -04:00
# define TFSM_STOP(abort,clear) { _transport_fsm->enqueue (new TransportFSM::Event (TransportFSM::StopTransport,abort,clear)); }
2021-04-28 21:54:51 -04:00
# define TFSM_LOCATE(target,ltd,loop,force) { _transport_fsm->enqueue (new TransportFSM::Event (TransportFSM::Locate,target,ltd,loop,force)); }
2021-06-18 13:21:15 -04:00
# define TFSM_SPEED(speed) { _transport_fsm->enqueue (new TransportFSM::Event (speed)); }
2019-09-17 20:26:03 -04:00
2019-03-13 11:26:17 -04:00
/* *****************************************************************************
2019-09-17 20:26:03 -04:00
* REALTIME ACTIONS ( to be called on state transitions )
2019-03-13 11:26:17 -04:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-11-08 11:28:21 -05:00
void
2019-03-13 11:26:17 -04:00
Session : : realtime_stop ( bool abort , bool clear_state )
2009-11-08 11:28:21 -05:00
{
2019-03-13 11:26:17 -04:00
ENSURE_PROCESS_THREAD ;
2009-11-08 11:28:21 -05:00
2021-05-03 19:35:34 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " realtime stop @ %1 speed = %2 \n " , _transport_sample , _transport_fsm - > transport_speed ( ) ) ) ;
2020-03-05 10:38:31 -05:00
PostTransportWork todo = PostTransportStop ;
2009-11-08 11:28:21 -05:00
2021-04-16 01:19:07 -04:00
/* we are rolling and we want to stop */
if ( Config - > get_monitoring_model ( ) = = HardwareMonitoring ) {
set_track_monitor_input_status ( true ) ;
}
if ( synced_to_engine ( ) ) {
if ( clear_state ) {
2022-06-13 12:20:50 -04:00
/* do this here because our response to the engine
transport master won ' t take care of it .
2021-04-16 01:19:07 -04:00
*/
_play_range = false ;
_count_in_once = false ;
unset_play_loop ( ) ;
}
}
2019-03-13 11:26:17 -04:00
/* call routes */
2009-11-08 11:28:21 -05:00
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > r = routes . reader ( ) ;
2019-03-13 11:26:17 -04:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * r ) {
i - > realtime_handle_transport_stopped ( ) ;
2008-06-02 17:41:35 -04:00
}
2009-10-30 14:14:25 -04:00
2019-03-13 11:26:17 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " stop complete, auto-return scheduled for return to %1 \n " , _requested_return_sample ) ) ;
2018-10-05 12:35:10 -04:00
2019-03-13 11:26:17 -04:00
if ( abort ) {
todo = PostTransportWork ( todo | PostTransportAbort ) ;
2018-09-18 18:51:59 -04:00
}
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
if ( clear_state ) {
todo = PostTransportWork ( todo | PostTransportClearSubstate ) ;
2018-09-18 18:51:59 -04:00
}
2019-03-13 11:26:17 -04:00
if ( todo ) {
add_post_transport_work ( todo ) ;
2011-02-06 20:12:47 -05:00
}
2023-06-08 10:44:21 -04:00
if ( clear_state ) {
_clear_event_type ( SessionEvent : : RangeStop ) ;
_clear_event_type ( SessionEvent : : RangeLocate ) ;
}
2011-02-06 20:12:47 -05:00
2019-03-13 11:26:17 -04:00
/* if we're going to clear loop state, then force disabling record BUT only if we're not doing latched rec-enable */
disable_record ( true , ( ! Config - > get_latched_record_enable ( ) & & clear_state ) ) ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
if ( clear_state & & ! Config - > get_loop_is_mode ( ) ) {
unset_play_loop ( ) ;
2018-09-18 18:51:59 -04:00
}
2020-02-27 16:16:12 -05:00
reset_punch_loop_constraint ( ) ;
2020-02-26 20:36:16 -05:00
2023-02-17 13:34:35 -05:00
_playback_load . store ( 100 ) ;
_capture_load . store ( 100 ) ;
2017-01-19 07:03:57 -05:00
2019-03-13 11:26:17 -04:00
if ( config . get_use_video_sync ( ) ) {
waiting_for_sync_offset = true ;
2017-01-19 07:03:57 -05:00
}
2019-09-17 20:26:03 -04:00
if ( todo ) {
2019-09-20 00:33:43 -04:00
TFSM_EVENT ( TransportFSM : : ButlerRequired ) ;
2017-02-13 16:59:20 -05:00
}
}
2019-11-15 17:47:29 -05:00
/** @param with_mmc true to send a MMC locate command when the locate is done */
2008-06-02 17:41:35 -04:00
void
2021-05-03 19:35:34 -04:00
Session : : locate ( samplepos_t target_sample , bool for_loop_end , bool force , bool with_mmc )
2008-06-02 17:41:35 -04:00
{
2019-03-13 11:26:17 -04:00
ENSURE_PROCESS_THREAD ;
if ( target_sample < 0 ) {
error < < _ ( " Locate called for negative sample position - ignored " ) < < endmsg ;
2015-12-15 02:39:09 -05:00
return ;
}
2019-03-13 11:26:17 -04:00
bool need_butler = false ;
2014-07-02 18:34:49 -04:00
2019-03-13 11:26:17 -04:00
/* Locates for seamless looping are fairly different from other
* locates . They assume that the diskstream buffers for each track
* already have the correct data in them , and thus there is no need to
* actually tell the tracks to locate . What does need to be done ,
* though , is all the housekeeping that is associated with non - linear
* changes in the value of _transport_sample .
*/
2021-05-03 19:35:34 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " rt-locate to %1 ts = %7, for loop end %2 force %3 mmc %4 \n " ,
target_sample , for_loop_end , force , with_mmc , _transport_sample ) ) ;
2019-03-13 11:26:17 -04:00
2020-05-12 13:30:12 -04:00
if ( ! force & & ( _transport_sample = = target_sample ) & & ! for_loop_end ) {
2019-09-20 00:33:43 -04:00
TFSM_EVENT ( TransportFSM : : LocateDone ) ;
2019-03-13 11:26:17 -04:00
Located ( ) ; /* EMIT SIGNAL */
return ;
2018-02-11 10:39:36 -05:00
}
2019-03-13 11:26:17 -04:00
// Update Timecode time
_transport_sample = target_sample ;
2019-11-11 20:21:47 -05:00
_nominal_jack_transport_sample = boost : : none ;
2019-03-13 11:26:17 -04:00
// Bump seek counter so that any in-process locate in the butler
// thread(s?) can restart.
2023-02-17 02:31:21 -05:00
_seek_counter . fetch_add ( 1 ) ;
2019-03-13 11:26:17 -04:00
_last_roll_or_reversal_location = target_sample ;
2023-05-26 20:21:40 -04:00
if ( ! for_loop_end & & ! _exporting ) {
2019-12-27 23:15:16 -05:00
_remaining_latency_preroll = worst_latency_preroll_buffer_size_ceil ( ) ;
2019-11-08 11:08:47 -05:00
}
2019-03-13 11:26:17 -04:00
timecode_time ( _transport_sample , transmitting_timecode_time ) ; // XXX here?
2018-09-18 18:51:59 -04:00
2021-05-03 19:35:34 -04:00
assert ( _transport_fsm - > locating ( ) | | _transport_fsm - > declicking_for_locate ( ) ) ;
2019-09-17 20:26:03 -04:00
2021-05-03 19:35:34 -04:00
/* Tell all routes to do the RT part of locate */
2019-09-17 20:26:03 -04:00
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > r = routes . reader ( ) ;
for ( auto const & i : * r ) {
i - > realtime_locate ( for_loop_end ) ;
2019-03-13 11:26:17 -04:00
}
2020-05-12 12:55:45 -04:00
if ( force | | ! for_loop_end ) {
2019-03-13 11:26:17 -04:00
PostTransportWork todo = PostTransportLocate ;
add_post_transport_work ( todo ) ;
need_butler = true ;
2018-02-11 10:39:36 -05:00
2019-03-13 11:26:17 -04:00
} else {
2018-09-18 18:51:59 -04:00
2019-03-13 11:26:17 -04:00
/* this is functionally what clear_clicks() does but with a tentative lock */
2018-09-18 18:51:59 -04:00
2019-03-13 11:26:17 -04:00
Glib : : Threads : : RWLock : : WriterLock clickm ( click_lock , Glib : : Threads : : TRY_LOCK ) ;
2018-02-11 10:39:36 -05:00
2019-03-13 11:26:17 -04:00
if ( clickm . locked ( ) ) {
2018-02-11 10:39:36 -05:00
2019-03-13 11:26:17 -04:00
for ( Clicks : : iterator i = clicks . begin ( ) ; i ! = clicks . end ( ) ; + + i ) {
delete * i ;
}
2009-11-08 11:28:21 -05:00
2019-03-13 11:26:17 -04:00
clicks . clear ( ) ;
}
}
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
/* cancel looped playback if transport pos outside of loop range */
2020-02-26 14:15:00 -05:00
if ( get_play_loop ( ) ) {
2010-12-07 09:44:47 -05:00
2019-03-13 11:26:17 -04:00
Location * al = _locations - > auto_loop_location ( ) ;
2010-12-07 09:44:47 -05:00
2019-03-13 11:26:17 -04:00
if ( al ) {
2020-09-20 18:34:09 -04:00
if ( _transport_sample < al - > start_sample ( ) | | _transport_sample > = al - > end_sample ( ) ) {
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
// located outside the loop: cancel looping directly, this is called from event handling context
2010-12-07 09:44:47 -05:00
2019-03-13 11:26:17 -04:00
have_looped = false ;
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
if ( ! Config - > get_loop_is_mode ( ) ) {
2019-12-05 15:00:24 -05:00
set_play_loop ( false , false ) ;
2019-03-13 11:26:17 -04:00
} else {
2019-11-01 16:04:16 -04:00
/* this will make the non_realtime_locate() in the butler
which then causes seek ( ) in tracks actually do the right
thing .
*/
set_track_loop ( false ) ;
2019-03-13 11:26:17 -04:00
}
2008-06-02 17:41:35 -04:00
2020-09-20 18:34:09 -04:00
} else if ( _transport_sample = = al - > start_sample ( ) ) {
2009-11-08 11:28:21 -05:00
2019-03-13 11:26:17 -04:00
// located to start of loop - this is looping, basically
2009-11-08 11:28:21 -05:00
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = routes . reader ( ) ;
2018-09-18 18:51:59 -04:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * rl ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2011-03-14 17:53:10 -04:00
2019-03-13 11:26:17 -04:00
if ( tr & & tr - > rec_enable_control ( ) - > get_value ( ) ) {
// tell it we've looped, so it can deal with the record state
tr - > transport_looped ( _transport_sample ) ;
}
}
2020-03-09 17:39:44 -04:00
if ( for_loop_end ) {
have_looped = true ;
TransportLooped ( ) ; // EMIT SIGNAL
}
2019-03-13 11:26:17 -04:00
}
}
2015-09-22 13:20:52 -04:00
}
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
if ( need_butler ) {
2019-09-20 00:33:43 -04:00
TFSM_EVENT ( TransportFSM : : ButlerRequired ) ;
2019-09-17 20:26:03 -04:00
} else {
2019-11-04 16:35:18 -05:00
TFSM_EVENT ( TransportFSM : : LocateDone ) ;
2019-03-13 11:26:17 -04:00
}
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
_send_timecode_update = true ;
2010-05-11 20:29:28 -04:00
2019-03-13 11:26:17 -04:00
if ( with_mmc ) {
send_mmc_locate ( _transport_sample ) ;
2008-06-02 17:41:35 -04:00
}
2019-03-13 11:26:17 -04:00
_last_roll_location = _last_roll_or_reversal_location = _transport_sample ;
2020-04-02 12:39:46 -04:00
Located ( ) ; /* EMIT SIGNAL */
2011-05-30 17:37:58 -04:00
}
2019-09-17 20:26:03 -04:00
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 ;
}
}
}
2021-06-18 14:22:47 -04:00
double
Session : : default_play_speed ( )
{
return _transport_fsm - > default_speed ( ) ;
}
2021-06-18 13:21:15 -04:00
/** Set the default speed that is used when we respond to a "play" action.
* @ param speed New speed
*/
void
2021-07-18 21:40:09 -04:00
Session : : set_default_play_speed ( double spd )
2021-06-18 13:21:15 -04:00
{
2021-07-19 13:17:57 -04:00
ENSURE_PROCESS_THREAD ;
2023-05-19 17:28:32 -04:00
if ( synced_to_engine ( ) ) {
if ( spd ! = 0 & & spd ! = 1 ) {
return ;
}
}
2021-07-19 11:28:25 -04:00
/* see also Port::set_speed_ratio and
* VMResampler : : set_rratio ( ) for min / max range .
* speed must be > + / - 100 / 16 %
*/
if ( spd > 0.0 ) {
spd = std : : min < double > ( Config - > get_max_transport_speed ( ) , std : : max ( 0.0625 , spd ) ) ;
} else if ( spd < 0.0 ) {
spd = std : : max < double > ( - Config - > get_max_transport_speed ( ) , std : : min ( - 0.0625 , spd ) ) ;
}
2021-06-18 13:21:15 -04:00
_transport_fsm - > set_default_speed ( spd ) ;
2021-06-22 13:48:56 -04:00
TFSM_SPEED ( spd ) ;
2021-06-18 13:21:15 -04:00
TransportStateChange ( ) ; /* EMIT SIGNAL */
}
2019-03-13 11:26:17 -04:00
/** Set the transport speed.
* Called from the process thread .
* @ param speed New speed
*/
2008-06-02 17:41:35 -04:00
void
2021-05-03 19:35:34 -04:00
Session : : set_transport_speed ( double speed )
2008-06-02 17:41:35 -04:00
{
2019-03-13 11:26:17 -04:00
ENSURE_PROCESS_THREAD ;
2021-05-03 19:35:34 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " @ %1 Set transport speed to %2 from %3 (es = %4) \n " , _transport_sample , speed , _transport_fsm - > transport_speed ( ) , _engine_speed ) ) ;
2020-02-20 02:25:25 -05:00
2023-05-19 17:28:32 -04:00
if ( synced_to_engine ( ) & & speed ! = 1.0 ) {
return ;
}
2021-06-22 14:03:32 -04:00
double default_speed = _transport_fsm - > default_speed ( ) ;
2021-04-19 17:45:47 -04:00
assert ( speed ! = 0.0 ) ;
2021-02-12 13:42:39 -05:00
/* the logic:
a ) engine speed is not 1.0 ( normal speed )
b ) engine speed matches the requested speed ( sign ignored )
c ) speed and transport speed have the same sign ( no direction change )
For ( c ) the correct arithmetical test is > = 0 , but we care about the
case where at least one of them is zero . That would generate an
equality with zero , but if only one of them is zero , we still need
to change speed . So we check that the product is > 0 , which implies
that neither of them are zero , and they have the same sign .
*/
2021-06-22 14:03:32 -04:00
if ( ( _engine_speed ! = default_speed ) & & ( _engine_speed = = fabs ( speed ) ) & & ( ( speed * _transport_fsm - > transport_speed ( ) ) > 0 ) ) {
2020-02-20 02:25:25 -05:00
/* engine speed is not changing and no direction change, do nothing */
DEBUG_TRACE ( DEBUG : : Transport , " no reason to change speed, do nothing \n " ) ;
return ;
}
2009-11-30 11:12:13 -05:00
2019-03-13 11:26:17 -04:00
/* max speed is somewhat arbitrary but based on guestimates regarding disk i/o capability
2020-02-20 02:25:25 -05:00
and user needs . XXX We really need CD - style " skip " playback for ffwd and rewind .
2019-03-13 11:26:17 -04:00
*/
2010-06-09 13:24:07 -04:00
2019-03-13 11:26:17 -04:00
if ( speed > 0 ) {
2019-07-11 01:32:14 -04:00
speed = min ( ( double ) Config - > get_max_transport_speed ( ) , speed ) ;
2019-03-13 11:26:17 -04:00
} else if ( speed < 0 ) {
2019-07-11 01:32:14 -04:00
speed = max ( ( double ) - Config - > get_max_transport_speed ( ) , speed ) ;
2019-03-13 11:26:17 -04:00
}
2015-09-28 17:42:02 -04:00
2021-04-19 17:45:47 -04:00
double new_engine_speed = fabs ( speed ) ;
2021-05-03 19:35:34 -04:00
// double new_transport_speed = (speed < 0) ? -1 : 1;
2008-06-02 17:41:35 -04:00
2021-04-16 01:14:42 -04:00
if ( ( synced_to_engine ( ) ) & & speed ! = 0.0 & & speed ! = 1.0 ) {
warning < < string_compose (
_ ( " Global varispeed cannot be supported while %1 is connected to JACK transport control " ) ,
PROGRAM_NAME )
< < endmsg ;
return ;
}
2008-06-02 17:41:35 -04:00
2021-05-03 19:35:34 -04:00
clear_clicks ( ) ;
_engine_speed = new_engine_speed ;
2017-07-04 12:14:33 -04:00
2021-06-24 11:36:19 -04:00
if ( ! Config - > get_auto_return_after_rewind_ffwd ( ) & & fabs ( speed ) > 2.0 ) {
/* fast-wind of any sort should cancel auto-return */
/* since we don't have an actual ffwd/rew state yet, just trigger on a 'fast' varispeed */
2021-05-04 20:54:40 -04:00
_requested_return_sample = - 1 ;
_last_roll_location = - 1 ;
_last_roll_or_reversal_location = - 1 ;
}
2021-04-16 01:14:42 -04:00
/* throttle signal emissions.
2021-05-03 19:35:34 -04:00
* when slaved [ _last ] _transport_fsm - > transport_speed ( )
2021-04-16 01:14:42 -04:00
* usually changes every cycle ( tiny amounts due to DLL ) .
* Emitting a signal every cycle is overkill and unwarranted .
*
2021-05-03 19:35:34 -04:00
* Using _transport_fsm - > transport_speed ( ) is not acceptable ,
2021-04-16 01:14:42 -04:00
* since it allows for large changes over a long period
* of time . Hence we introduce a dedicated variable to keep track
*
* The 0.2 % dead - zone is somewhat arbitrary . Main use - case
* for TransportStateChange ( ) here is the ShuttleControl display .
*/
2021-04-19 17:45:47 -04:00
const double act_speed = actual_speed ( ) ;
if ( fabs ( _signalled_varispeed - act_speed ) > .002
2021-04-16 01:14:42 -04:00
// still, signal hard changes to 1.0 and 0.0:
2021-06-22 14:03:32 -04:00
| | ( act_speed = = default_speed & & _signalled_varispeed ! = default_speed )
2021-04-19 17:45:47 -04:00
| | ( act_speed = = 0.0 & & _signalled_varispeed ! = 0.0 )
2021-04-16 01:14:42 -04:00
)
{
2021-05-04 20:55:09 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " send TSC3 with speed = %1 \n " , _transport_fsm - > transport_speed ( ) ) ) ;
2021-04-16 01:14:42 -04:00
TransportStateChange ( ) ; /* EMIT SIGNAL */
2021-04-19 17:45:47 -04:00
_signalled_varispeed = act_speed ;
2017-07-21 17:07:51 -04:00
}
2021-05-04 20:55:09 -04:00
2008-06-02 17:41:35 -04:00
}
2021-12-12 12:32:16 -05:00
/** Called from the gui thread */
void
2022-09-27 10:09:31 -04:00
Session : : trigger_stop_all ( bool now )
2021-12-12 12:32:16 -05:00
{
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = routes . reader ( ) ;
2022-03-13 12:22:20 -04:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * rl ) {
i - > stop_triggers ( now ) ;
2021-12-12 12:32:16 -05:00
}
2022-03-13 12:22:20 -04:00
if ( TriggerBox : : cue_recording ( ) ) {
CueRecord cr ( INT32_MAX , _transport_sample ) ;
TriggerBox : : cue_records . write ( & cr , 1 ) ;
}
2021-12-12 12:32:16 -05:00
}
2019-03-13 11:26:17 -04:00
/** Stop the transport. */
void
Session : : stop_transport ( bool abort , bool clear_state )
2015-01-16 12:17:09 -05:00
{
2019-03-13 11:26:17 -04:00
ENSURE_PROCESS_THREAD ;
2015-01-16 17:50:10 -05:00
2019-03-13 11:26:17 -04:00
_count_in_once = false ;
2015-01-16 12:17:09 -05:00
2019-09-17 20:26:03 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " time to actually stop with TS @ %1 \n " , _transport_sample ) ) ;
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
realtime_stop ( abort , clear_state ) ;
}
2015-05-12 23:17:14 -04:00
2019-03-13 11:26:17 -04:00
/** Called from the process thread */
void
2021-07-26 19:21:57 -04:00
Session : : start_transport ( bool after_loop )
2019-03-13 11:26:17 -04:00
{
ENSURE_PROCESS_THREAD ;
DEBUG_TRACE ( DEBUG : : Transport , " start_transport \n " ) ;
2015-05-12 23:17:14 -04:00
2021-04-16 01:19:07 -04:00
if ( Config - > get_loop_is_mode ( ) & & get_play_loop ( ) ) {
Location * location = _locations - > auto_loop_location ( ) ;
if ( location ! = 0 ) {
2021-04-20 15:40:32 -04:00
if ( _transport_sample ! = location - > start_sample ( ) ) {
2021-04-16 01:19:07 -04:00
/* force tracks to do their thing */
set_track_loop ( true ) ;
/* jump to start and then roll from there */
2022-05-27 20:49:11 -04:00
request_locate ( location - > start_sample ( ) , false , MustRoll ) ;
2021-04-16 01:19:07 -04:00
return ;
}
}
}
2021-05-03 19:35:34 -04:00
if ( Config - > get_monitoring_model ( ) = = HardwareMonitoring ) {
set_track_monitor_input_status ( ! config . get_auto_input ( ) ) ;
2021-04-16 01:19:07 -04:00
}
2019-03-13 11:26:17 -04:00
_last_roll_location = _transport_sample ;
_last_roll_or_reversal_location = _transport_sample ;
2020-03-05 15:17:25 -05:00
if ( ! have_looped & & ! _exporting ) {
2020-01-25 23:15:24 -05:00
_remaining_latency_preroll = worst_latency_preroll_buffer_size_ceil ( ) ;
}
2019-03-13 11:26:17 -04:00
have_looped = false ;
/* if record status is Enabled, move it to Recording. if its
already Recording , move it to Disabled .
2015-01-16 12:17:09 -05:00
*/
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
switch ( record_status ( ) ) {
case Enabled :
if ( ! config . get_punch_in ( ) ) {
/* This is only for UIs (keep blinking rec-en before
* punch - in , don ' t show rec - region etc ) . The UI still
* depends on SessionEvent : : PunchIn and ensuing signals .
*
* The disk - writers handle punch in / out internally
* in their local delay - compensated timeframe .
*/
enable_record ( ) ;
}
break ;
case Recording :
2020-02-26 14:15:00 -05:00
if ( ! get_play_loop ( ) ) {
2019-03-13 11:26:17 -04:00
disable_record ( false ) ;
2015-01-16 12:17:09 -05:00
}
2019-03-13 11:26:17 -04:00
break ;
default :
break ;
2015-01-16 12:17:09 -05:00
}
2015-10-05 10:17:49 -04:00
2020-02-26 20:36:16 -05:00
maybe_allow_only_loop ( ) ;
maybe_allow_only_punch ( ) ;
2021-04-19 17:45:59 -04:00
clear_clicks ( ) ;
2019-03-13 11:26:17 -04:00
if ( ! _engine . freewheeling ( ) ) {
Timecode : : Time time ;
timecode_time_subframes ( _transport_sample , time ) ;
2020-01-27 15:49:53 -05:00
if ( transport_master ( ) - > type ( ) ! = MTC ) { // why not when slaved to MTC?
2019-03-13 11:26:17 -04:00
send_immediate_mmc ( MIDI : : MachineControlCommand ( MIDI : : MachineControl : : cmdDeferredPlay ) ) ;
}
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
if ( ( actively_recording ( ) | | ( config . get_punch_in ( ) & & get_record_enabled ( ) ) )
& & click_data & & ( config . get_count_in ( ) | | _count_in_once ) ) {
2020-11-27 14:40:58 -05:00
TempoMap : : SharedPtr tmap ( TempoMap : : use ( ) ) ;
2019-03-13 11:26:17 -04:00
_count_in_once = false ;
/* calculate count-in duration (in audio samples)
* - use [ fixed ] tempo / meter at _transport_sample
* - calc duration of 1 bar + time - to - beat before or at transport_sample
*/
2023-06-12 14:36:16 -04:00
TempoMetric const & tempometric = tmap - > metric_at ( timepos_t ( _transport_sample ) ) ;
2015-05-12 23:17:14 -04:00
2020-11-11 11:31:52 -05:00
const double num = tempometric . divisions_per_bar ( ) ;
/* XXX possible optimization: get meter and BBT time in one call */
2021-03-25 12:24:05 -04:00
const Temporal : : BBT_Time bbt = tmap - > bbt_at ( timepos_t ( _transport_sample ) ) ;
2022-06-20 15:13:52 -04:00
const double bar_fract = ( bbt . beats - 1.0 + bbt . ticks / ( double ) Temporal : : ticks_per_beat ) / tempometric . divisions_per_bar ( ) ;
2019-03-13 11:26:17 -04:00
2020-11-11 11:31:52 -05:00
_count_in_samples = tempometric . samples_per_bar ( _current_sample_rate ) ;
2019-03-13 11:26:17 -04:00
double dt = _count_in_samples / num ;
if ( bar_fract = = 0 ) {
/* at bar boundary, count-in 2 bars before start. */
_count_in_samples * = 2 ;
} else {
/* beats left after full bar until roll position */
_count_in_samples * = 1. + bar_fract ;
}
if ( _count_in_samples > _remaining_latency_preroll ) {
_remaining_latency_preroll = _count_in_samples ;
}
int clickbeat = 0 ;
samplepos_t cf = _transport_sample - _count_in_samples ;
samplecnt_t offset = _click_io - > connected_latency ( true ) ;
2020-03-07 21:50:57 -05:00
clear_clicks ( ) ;
_clicks_cleared = cf ;
2019-03-13 11:26:17 -04:00
while ( cf < _transport_sample + offset ) {
add_click ( cf , clickbeat = = 0 ) ;
cf + = dt ;
clickbeat = fmod ( clickbeat + 1 , num ) ;
}
if ( _count_in_samples < _remaining_latency_preroll ) {
_count_in_samples = _remaining_latency_preroll ;
2015-10-04 14:51:05 -04:00
}
2015-01-16 12:17:09 -05:00
}
}
2015-10-05 10:17:49 -04:00
2021-05-03 19:35:34 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " send TSC4 with speed = %1 \n " , transport_speed ( ) ) ) ;
2021-07-19 13:17:57 -04:00
2021-07-26 19:21:57 -04:00
if ( ! after_loop ) {
/* emit TransportStateChange signal only when transport is actually rolling */
SessionEvent * ev = new SessionEvent ( SessionEvent : : TransportStateChange , SessionEvent : : Add , _transport_sample , _transport_sample , 1.0 ) ;
2021-07-19 13:17:57 -04:00
queue_event ( ev ) ;
2021-07-26 19:21:57 -04:00
2022-05-12 10:15:15 -04:00
samplepos_t roll_pos = _transport_sample + std : : max ( _count_in_samples , _remaining_latency_preroll ) * ( _transport_fsm - > will_roll_forwards ( ) ? 1 : - 1 ) ;
2021-07-26 19:21:57 -04:00
if ( roll_pos > 0 & & roll_pos ! = _transport_sample ) {
/* and when transport_rolling () == true */
SessionEvent * ev = new SessionEvent ( SessionEvent : : TransportStateChange , SessionEvent : : Add , roll_pos , roll_pos , 1.0 ) ;
queue_event ( ev ) ;
}
2021-07-19 13:17:57 -04:00
}
2019-03-13 11:26:17 -04:00
}
2020-07-18 19:29:53 -04:00
bool
Session : : need_declick_before_locate ( ) const
{
/* At this time (July 2020) only audio playback from disk readers is
de - clicked . MIDI tracks with audio output really need it too .
*/
return naudiotracks ( ) > 0 ;
}
2021-05-03 19:35:34 -04:00
bool
Session : : should_stop_before_locate ( ) const
{
/* do "stopped" stuff if:
*
* we are rolling AND
* no autoplay in effect AND
* we ' re not synced to an external transport master
*
*/
if ( ( ! auto_play_legal | | ! config . get_auto_play ( ) ) & &
! ( config . get_external_sync ( ) & & ! synced_to_engine ( ) ) ) {
return true ;
}
return false ;
}
bool
Session : : user_roll_after_locate ( ) const
{
return auto_play_legal & & config . get_auto_play ( ) ;
}
2019-09-17 20:26:03 -04:00
bool
Session : : should_roll_after_locate ( ) const
{
2019-11-15 17:48:29 -05:00
/* a locate must previously have been requested and completed before
* this answer can be considered correct
*/
2019-09-17 20:26:03 -04:00
2021-05-03 19:32:27 -04:00
return ( ( ! config . get_external_sync ( ) & & ( auto_play_legal & & config . get_auto_play ( ) ) ) & & ! _exporting ) ;
2019-09-17 20:26:03 -04:00
}
2019-03-13 11:26:17 -04:00
/** Do any transport work in the audio thread that needs to be done after the
2019-09-17 20:26:03 -04:00
* butler thread is finished . Audio thread , realtime safe .
2019-03-13 11:26:17 -04:00
*/
void
2019-09-17 20:26:03 -04:00
Session : : butler_completed_transport_work ( )
2019-03-13 11:26:17 -04:00
{
ENSURE_PROCESS_THREAD ;
PostTransportWork ptw = post_transport_work ( ) ;
2019-09-17 20:26:03 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Butler done, RT cleanup for %1 \n " , enum_2_string ( ptw ) ) ) ;
2019-03-13 11:26:17 -04:00
if ( ptw & PostTransportAudition ) {
if ( auditioner & & auditioner - > auditioning ( ) ) {
2020-04-22 23:19:01 -04:00
_remaining_latency_preroll = 0 ;
2019-03-13 11:26:17 -04:00
process_function = & Session : : process_audition ;
} else {
process_function = & Session : : process_with_events ;
2015-01-16 12:17:09 -05:00
}
2019-09-22 14:18:36 -04:00
ptw = PostTransportWork ( ptw & ~ PostTransportAudition ) ;
set_post_transport_work ( ptw ) ;
2015-10-04 14:51:05 -04:00
}
2015-01-16 12:17:09 -05:00
2019-03-13 11:26:17 -04:00
if ( ptw & PostTransportLocate ) {
2019-09-17 20:26:03 -04:00
post_locate ( ) ;
2019-09-22 14:18:36 -04:00
ptw = PostTransportWork ( ptw & ~ PostTransportLocate ) ;
set_post_transport_work ( ptw ) ;
2019-09-20 00:33:43 -04:00
TFSM_EVENT ( TransportFSM : : LocateDone ) ;
2019-09-17 20:26:03 -04:00
}
2015-06-25 13:12:47 -04:00
2020-01-21 23:50:18 -05:00
/* the butler finished its work so clear all PostTransportWork flags
*/
2020-01-22 13:16:11 -05:00
set_post_transport_work ( PostTransportWork ( 0 ) ) ;
2019-09-22 14:18:36 -04:00
2019-03-13 11:26:17 -04:00
set_next_event ( ) ;
2019-09-17 20:26:03 -04:00
if ( _transport_fsm - > waiting_for_butler ( ) ) {
2019-09-20 00:33:43 -04:00
TFSM_EVENT ( TransportFSM : : ButlerDone ) ;
2019-09-17 20:26:03 -04:00
}
}
void
Session : : schedule_butler_for_transport_work ( )
{
assert ( _transport_fsm - > waiting_for_butler ( ) ) ;
2020-07-05 19:47:12 -04:00
DEBUG_TRACE ( DEBUG : : Butler , string_compose ( " summon butler for transport work (%1) \n " , enum_2_string ( post_transport_work ( ) ) ) ) ;
2019-09-17 20:26:03 -04:00
_butler - > schedule_transport_work ( ) ;
2015-06-25 13:12:47 -04:00
}
2019-03-13 11:26:17 -04:00
bool
Session : : maybe_stop ( samplepos_t limit )
2015-01-16 12:17:09 -05:00
{
2019-03-13 11:26:17 -04:00
ENSURE_PROCESS_THREAD ;
2021-05-03 19:35:34 -04:00
if ( ( _transport_fsm - > transport_speed ( ) > 0.0f & & _transport_sample > = limit ) | | ( _transport_fsm - > transport_speed ( ) < 0.0f & & _transport_sample = = 0 ) ) {
2019-11-15 17:49:08 -05:00
if ( synced_to_engine ( ) ) {
2019-03-13 11:26:17 -04:00
_engine . transport_stop ( ) ;
2019-11-15 17:49:08 -05:00
} else {
2020-01-28 15:04:39 -05:00
TFSM_STOP ( false , false ) ;
2019-03-13 11:26:17 -04:00
}
return true ;
2015-01-16 12:17:09 -05:00
}
2019-03-13 11:26:17 -04:00
return false ;
2015-01-16 12:17:09 -05:00
}
2019-03-13 11:26:17 -04:00
int
Session : : micro_locate ( samplecnt_t distance )
2008-06-02 17:41:35 -04:00
{
2019-03-13 11:26:17 -04:00
ENSURE_PROCESS_THREAD ;
2008-06-02 17:41:35 -04:00
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = routes . reader ( ) ;
for ( auto const & i : * rl ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2019-03-13 11:26:17 -04:00
if ( tr & & ! tr - > can_internal_playback_seek ( distance ) ) {
return - 1 ;
2008-06-02 17:41:35 -04:00
}
}
2019-03-13 11:26:17 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " micro-locate by %1 \n " , distance ) ) ;
2010-07-16 10:55:11 -04:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * rl ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2010-04-21 16:42:22 -04:00
if ( tr ) {
2019-03-13 11:26:17 -04:00
tr - > internal_playback_seek ( distance ) ;
2010-04-21 16:42:22 -04:00
}
2008-06-02 17:41:35 -04:00
}
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
_transport_sample + = distance ;
return 0 ;
}
2010-07-16 10:55:11 -04:00
2019-03-13 11:26:17 -04:00
void
Session : : flush_all_inserts ( )
{
ENSURE_PROCESS_THREAD ;
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > r = routes . reader ( ) ;
2008-06-02 17:41:35 -04:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * r ) {
i - > flush_processors ( ) ;
2008-08-04 18:37:24 -04:00
}
2019-03-13 11:26:17 -04:00
}
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
/* *****************************************************************************
* END REALTIME ACTIONS
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
void
Session : : add_post_transport_work ( PostTransportWork ptw )
{
PostTransportWork oldval ;
PostTransportWork newval ;
int tries = 0 ;
2019-02-28 19:18:08 -05:00
2019-03-13 11:26:17 -04:00
while ( tries < 8 ) {
2023-02-17 02:31:21 -05:00
oldval = _post_transport_work . load ( ) ;
2019-03-13 11:26:17 -04:00
newval = PostTransportWork ( oldval | ptw ) ;
2023-02-17 02:31:21 -05:00
if ( _post_transport_work . compare_exchange_strong ( oldval , newval ) ) {
2019-03-13 11:26:17 -04:00
/* success */
return ;
}
}
2019-02-28 19:18:08 -05:00
2019-03-13 11:26:17 -04:00
error < < " Could not set post transport work! Crazy thread madness, call the programmers " < < endmsg ;
}
2019-02-28 19:18:08 -05:00
2019-03-13 11:26:17 -04:00
bool
2020-04-24 16:20:59 -04:00
Session : : should_ignore_transport_request ( TransportRequestSource src , TransportRequestType type )
2019-03-13 11:26:17 -04:00
{
if ( config . get_external_sync ( ) ) {
if ( TransportMasterManager : : instance ( ) . current ( ) - > allow_request ( src , type ) ) {
2020-04-24 16:22:06 -04:00
/* accepting a command means dropping external sync first */
config . set_external_sync ( false ) ;
2019-03-13 11:26:17 -04:00
return true ;
2019-02-28 19:18:08 -05:00
}
2009-10-28 19:56:05 -04:00
}
2019-03-13 11:26:17 -04:00
return false ;
}
2009-10-28 19:56:05 -04:00
2019-03-13 11:26:17 -04:00
bool
2019-11-15 17:50:33 -05:00
Session : : synced_to_engine ( ) const
{
2019-03-13 11:26:17 -04:00
return config . get_external_sync ( ) & & TransportMasterManager : : instance ( ) . current ( ) - > type ( ) = = Engine ;
}
2012-01-23 21:30:46 -05:00
2019-03-13 11:26:17 -04:00
void
2023-02-16 18:33:28 -05:00
Session : : request_sync_source ( std : : shared_ptr < TransportMaster > tm )
2019-03-13 11:26:17 -04:00
{
SessionEvent * ev = new SessionEvent ( SessionEvent : : SetTransportMaster , SessionEvent : : Add , SessionEvent : : Immediate , 0 , 0.0 ) ;
ev - > transport_master = tm ;
DEBUG_TRACE ( DEBUG : : Slave , " sent request for new transport master \n " ) ;
queue_event ( ev ) ;
}
2009-11-08 11:28:21 -05:00
2021-04-16 01:09:00 -04:00
void
Session : : reset_transport_speed ( TransportRequestSource origin )
{
2021-06-18 13:21:15 -04:00
request_transport_speed ( _transport_fsm - > default_speed ( ) , origin ) ;
2021-04-16 01:09:00 -04:00
}
2019-03-13 11:26:17 -04:00
void
2021-06-18 13:21:15 -04:00
Session : : request_transport_speed ( double speed , TransportRequestSource origin )
2019-03-13 11:26:17 -04:00
{
2019-11-15 17:50:05 -05:00
if ( synced_to_engine ( ) ) {
if ( speed ! = 0 ) {
_engine . transport_start ( ) ;
} else {
_engine . transport_stop ( ) ;
}
return ;
}
2020-03-10 13:39:39 -04:00
if ( speed = = 1. | | speed = = 0. | | speed = = - 1. ) {
if ( should_ignore_transport_request ( origin , TR_StartStop ) ) {
return ;
}
} else {
if ( should_ignore_transport_request ( origin , TR_Speed ) ) {
return ;
}
2009-11-08 11:28:21 -05:00
}
2019-11-15 17:50:05 -05:00
2019-03-13 11:26:17 -04:00
SessionEvent * ev = new SessionEvent ( SessionEvent : : SetTransportSpeed , SessionEvent : : Add , SessionEvent : : Immediate , 0 , speed ) ;
2021-06-18 13:21:15 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Request transport speed = %1 as default = %2 \n " , speed ) ) ;
2019-03-13 11:26:17 -04:00
queue_event ( ev ) ;
}
2009-11-08 11:28:21 -05:00
2021-07-18 21:40:09 -04:00
void
2021-07-18 21:48:13 -04:00
Session : : request_default_play_speed ( double speed , TransportRequestSource origin )
2021-07-18 21:40:09 -04:00
{
if ( synced_to_engine ( ) ) {
return ;
}
2021-07-18 21:48:13 -04:00
SessionEvent * ev = new SessionEvent ( SessionEvent : : SetDefaultPlaySpeed , SessionEvent : : Add , SessionEvent : : Immediate , 0 , speed ) ;
2021-07-18 21:40:09 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Request default transport speed = %1 as default = %2 \n " , speed ) ) ;
queue_event ( ev ) ;
}
2019-03-13 11:26:17 -04:00
/** Request a new transport speed, but if the speed parameter is exactly zero then use
* a very small + ve value to prevent the transport actually stopping . This method should
* be used by callers who are varying transport speed but don ' t ever want to stop it .
*/
void
2021-06-18 13:21:15 -04:00
Session : : request_transport_speed_nonzero ( double speed , TransportRequestSource origin )
2019-03-13 11:26:17 -04:00
{
if ( speed = = 0 ) {
speed = DBL_EPSILON ;
2009-10-28 19:53:16 -04:00
}
2008-06-02 17:41:35 -04:00
2021-06-18 13:21:15 -04:00
request_transport_speed ( speed ) ;
2019-03-13 11:26:17 -04:00
}
2021-04-12 20:57:25 -04:00
void
Session : : request_roll ( TransportRequestSource origin )
{
if ( synced_to_engine ( ) ) {
2021-06-03 03:53:39 -04:00
_engine . transport_start ( ) ;
2021-04-12 20:57:25 -04:00
return ;
}
if ( should_ignore_transport_request ( origin , TR_StartStop ) ) {
return ;
}
2021-05-03 19:35:34 -04:00
SessionEvent * ev = new SessionEvent ( SessionEvent : : StartRoll , SessionEvent : : Add , SessionEvent : : Immediate , 0 , false ) ; /* final 2 argumment do not matter */
DEBUG_TRACE ( DEBUG : : Transport , " Request transport roll \n " ) ;
2021-04-12 20:57:25 -04:00
queue_event ( ev ) ;
}
2019-03-13 11:26:17 -04:00
void
Session : : request_stop ( bool abort , bool clear_state , TransportRequestSource origin )
{
2019-11-15 17:50:05 -05:00
if ( synced_to_engine ( ) ) {
_engine . transport_stop ( ) ;
return ;
}
2020-03-10 13:39:39 -04:00
if ( should_ignore_transport_request ( origin , TR_StartStop ) ) {
2019-03-13 11:26:17 -04:00
return ;
2017-07-21 17:07:51 -04:00
}
2020-06-19 18:11:10 -04:00
/* clear our solo-selection, if there is one */
if ( solo_selection_active ( ) ) {
solo_selection ( _soloSelection , false ) ;
}
2021-04-16 01:19:33 -04:00
SessionEvent * ev = new SessionEvent ( SessionEvent : : EndRoll , SessionEvent : : Add , SessionEvent : : Immediate , audible_sample ( ) , 0.0 , abort , clear_state ) ;
2019-03-13 11:26:17 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Request transport stop, audible %3 transport %4 abort = %1, clear state = %2 \n " , abort , clear_state , audible_sample ( ) , _transport_sample ) ) ;
queue_event ( ev ) ;
}
2008-12-12 09:43:24 -05:00
2019-03-13 11:26:17 -04:00
void
2022-05-27 20:49:11 -04:00
Session : : request_locate ( samplepos_t target_sample , bool force , LocateTransportDisposition ltd , TransportRequestSource origin )
2019-03-13 11:26:17 -04:00
{
2019-11-15 17:50:05 -05:00
if ( synced_to_engine ( ) ) {
_engine . transport_locate ( target_sample ) ;
return ;
}
2019-03-13 11:26:17 -04:00
if ( should_ignore_transport_request ( origin , TR_Locate ) ) {
return ;
}
2013-08-09 18:06:08 -04:00
2020-01-17 17:26:01 -05:00
SessionEvent : : Type type ;
switch ( ltd ) {
case MustRoll :
type = SessionEvent : : LocateRoll ;
break ;
case MustStop :
type = SessionEvent : : Locate ;
break ;
2020-01-17 18:35:28 -05:00
case RollIfAppropriate :
2020-01-17 17:26:01 -05:00
if ( config . get_auto_play ( ) ) {
type = SessionEvent : : LocateRoll ;
} else {
type = SessionEvent : : Locate ;
}
break ;
2022-06-21 20:38:11 -04:00
default :
/* impossible, but gcc -O3 can't figure it out */
return ;
2020-01-17 17:26:01 -05:00
}
2023-06-08 12:37:27 -04:00
if ( type = = SessionEvent : : LocateRoll ) {
request_cancel_play_range ( ) ;
}
2022-05-27 20:49:11 -04:00
SessionEvent * ev = new SessionEvent ( type , SessionEvent : : Add , SessionEvent : : Immediate , target_sample , 0 , force ) ;
2020-01-17 17:26:01 -05:00
ev - > locate_transport_disposition = ltd ;
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Request locate to %1 ltd = %2 \n " , target_sample , enum_2_string ( ltd ) ) ) ;
2019-03-13 11:26:17 -04:00
queue_event ( ev ) ;
}
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
void
2020-01-20 13:07:27 -05:00
Session : : force_locate ( samplepos_t target_sample , LocateTransportDisposition ltd )
2019-03-13 11:26:17 -04:00
{
2020-01-20 13:07:27 -05:00
SessionEvent * ev = new SessionEvent ( SessionEvent : : Locate , SessionEvent : : Add , SessionEvent : : Immediate , target_sample , 0 , true ) ;
ev - > locate_transport_disposition = ltd ;
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Request forced locate to %1 roll %2 \n " , target_sample , enum_2_string ( ltd ) ) ) ;
2019-03-13 11:26:17 -04:00
queue_event ( ev ) ;
}
2013-08-09 18:06:08 -04:00
2019-03-13 11:26:17 -04:00
void
Session : : unset_preroll_record_trim ( )
{
_preroll_record_trim_len = 0 ;
}
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
void
Session : : request_preroll_record_trim ( samplepos_t rec_in , samplecnt_t preroll )
{
if ( actively_recording ( ) ) {
return ;
2011-03-14 17:53:10 -04:00
}
2019-03-13 11:26:17 -04:00
unset_preroll_record_trim ( ) ;
2008-12-12 09:43:24 -05:00
2019-03-13 11:26:17 -04:00
config . set_punch_in ( false ) ;
config . set_punch_out ( false ) ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
samplepos_t pos = std : : max ( ( samplepos_t ) 0 , rec_in - preroll ) ;
2021-07-11 20:11:35 -04:00
_preroll_record_trim_len = rec_in - pos ;
2019-03-13 11:26:17 -04:00
maybe_enable_record ( ) ;
2022-05-27 20:49:11 -04:00
request_locate ( pos , false , MustRoll ) ;
2019-03-13 11:26:17 -04:00
set_requested_return_sample ( rec_in ) ;
2021-06-30 21:12:27 -04:00
if ( pos < rec_in ) {
/* Notify GUI to update monitor state display */
SessionEvent * ev = new SessionEvent ( SessionEvent : : TransportStateChange , SessionEvent : : Add , rec_in , rec_in , 1.0 ) ;
queue_event ( ev ) ;
}
2019-03-13 11:26:17 -04:00
}
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
void
Session : : request_count_in_record ( )
{
if ( actively_recording ( ) ) {
return ;
}
if ( transport_rolling ( ) ) {
return ;
}
maybe_enable_record ( ) ;
_count_in_once = true ;
2021-06-18 13:21:15 -04:00
request_transport_speed ( _transport_fsm - > default_speed ( ) ) ;
2021-07-11 01:11:32 -04:00
request_roll ( ) ;
2019-03-13 11:26:17 -04:00
}
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
void
Session : : request_play_loop ( bool yn , bool change_transport_roll )
{
if ( transport_master_is_external ( ) & & yn ) {
// don't attempt to loop when not using Internal Transport
// see also gtk2_ardour/ardour_ui_options.cc parameter_changed()
return ;
}
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
SessionEvent * ev ;
Location * location = _locations - > auto_loop_location ( ) ;
double target_speed ;
if ( location = = 0 & & yn ) {
error < < _ ( " Cannot loop - no loop range defined " )
< < endmsg ;
return ;
2008-06-02 17:41:35 -04:00
}
2019-03-13 11:26:17 -04:00
if ( change_transport_roll ) {
if ( transport_rolling ( ) ) {
/* start looping at current speed */
target_speed = transport_speed ( ) ;
} else {
/* currently stopped */
if ( yn ) {
/* start looping at normal speed */
2021-06-22 14:03:32 -04:00
target_speed = _transport_fsm - > default_speed ( ) ;
2019-03-13 11:26:17 -04:00
} else {
target_speed = 0.0 ;
}
2014-04-17 09:47:06 -04:00
}
2019-03-13 11:26:17 -04:00
} else {
/* leave the speed alone */
target_speed = transport_speed ( ) ;
2008-06-02 17:41:35 -04:00
}
2019-12-05 15:00:24 -05:00
ev = new SessionEvent ( SessionEvent : : SetLoop , SessionEvent : : Add , SessionEvent : : Immediate , 0 , target_speed , yn , change_transport_roll ) ;
2019-03-13 11:26:17 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Request set loop = %1, change roll state ? %2 \n " , yn , change_transport_roll ) ) ;
queue_event ( ev ) ;
}
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
void
2020-09-20 18:34:09 -04:00
Session : : request_play_range ( list < TimelineRange > * range , bool leave_rolling )
2019-03-13 11:26:17 -04:00
{
2021-06-22 14:03:32 -04:00
SessionEvent * ev = new SessionEvent ( SessionEvent : : SetPlayAudioRange , SessionEvent : : Add , SessionEvent : : Immediate , 0 , ( leave_rolling ? _transport_fsm - > default_speed ( ) : 0.0 ) ) ;
2019-03-13 11:26:17 -04:00
if ( range ) {
ev - > audio_range = * range ;
} else {
ev - > audio_range . clear ( ) ;
2008-06-02 17:41:35 -04:00
}
2019-03-13 11:26:17 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Request play range, leave rolling ? %1 \n " , leave_rolling ) ) ;
queue_event ( ev ) ;
2008-06-02 17:41:35 -04:00
}
void
2019-03-13 11:26:17 -04:00
Session : : request_cancel_play_range ( )
2009-11-08 11:28:21 -05:00
{
2019-03-13 11:26:17 -04:00
SessionEvent * ev = new SessionEvent ( SessionEvent : : CancelPlayAudioRange , SessionEvent : : Add , SessionEvent : : Immediate , 0 , 0 ) ;
queue_event ( ev ) ;
}
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
bool
Session : : solo_selection_active ( )
{
2019-09-17 20:26:03 -04:00
if ( _soloSelection . empty ( ) ) {
2019-03-13 11:26:17 -04:00
return false ;
2015-02-16 19:30:21 -05:00
}
2019-03-13 11:26:17 -04:00
return true ;
2015-02-13 15:27:53 -05:00
}
void
2019-09-17 20:26:03 -04:00
Session : : solo_selection ( StripableList & list , bool new_state )
2015-02-13 15:27:53 -05:00
{
2023-02-16 18:33:28 -05:00
std : : shared_ptr < ControlList > solo_list ( new ControlList ) ;
std : : shared_ptr < ControlList > unsolo_list ( new ControlList ) ;
2011-06-01 12:50:12 -04:00
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = get_routes ( ) ;
2015-02-16 19:30:21 -05:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * rl ) {
2019-03-13 11:26:17 -04:00
2023-04-07 17:33:13 -04:00
if ( ! i - > is_track ( ) ) {
2019-03-13 11:26:17 -04:00
continue ;
}
2023-04-07 17:33:13 -04:00
std : : shared_ptr < Stripable > s ( i ) ;
2019-03-13 11:26:17 -04:00
bool found = ( std : : find ( list . begin ( ) , list . end ( ) , s ) ! = list . end ( ) ) ;
2020-06-19 18:11:10 -04:00
if ( found ) {
/* must invalidate playlists on selected track, so disk reader
* will re - fill with the new selection state for solo_selection */
2023-04-07 17:33:13 -04:00
std : : shared_ptr < Track > track = std : : dynamic_pointer_cast < Track > ( i ) ;
2019-03-13 11:26:17 -04:00
if ( track ) {
2023-02-16 18:33:28 -05:00
std : : shared_ptr < Playlist > playlist = track - > playlist ( ) ;
2019-03-13 11:26:17 -04:00
if ( playlist ) {
playlist - > ContentsChanged ( ) ;
}
}
2020-06-19 18:11:10 -04:00
}
if ( found & new_state ) {
solo_list - > push_back ( s - > solo_control ( ) ) ;
2019-03-13 11:26:17 -04:00
} else {
unsolo_list - > push_back ( s - > solo_control ( ) ) ;
2009-11-08 11:28:21 -05:00
}
}
2019-03-13 11:26:17 -04:00
2020-06-19 18:11:10 -04:00
/* set/unset solos of the associated tracks */
2019-03-13 11:26:17 -04:00
set_controls ( solo_list , 1.0 , Controllable : : NoGroup ) ;
set_controls ( unsolo_list , 0.0 , Controllable : : NoGroup ) ;
2020-06-19 18:11:10 -04:00
if ( new_state )
_soloSelection = list ;
else
_soloSelection . clear ( ) ;
2009-11-08 11:28:21 -05:00
}
2019-03-13 11:26:17 -04:00
2009-11-08 11:28:21 -05:00
void
2020-12-07 19:08:49 -05:00
Session : : butler_transport_work ( bool have_process_lock )
2008-06-02 17:41:35 -04:00
{
2019-03-13 11:26:17 -04:00
/* Note: this function executes in the butler thread context */
2011-06-01 12:50:12 -04:00
2019-03-27 12:58:12 -04:00
restart :
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > r = routes . reader ( ) ;
2023-02-17 13:34:35 -05:00
int on_entry = _butler - > should_do_transport_work . load ( ) ;
2019-03-27 12:58:12 -04:00
bool finished = true ;
PostTransportWork ptw = post_transport_work ( ) ;
2020-01-22 13:15:45 -05:00
# ifndef NDEBUG
2022-01-20 15:39:13 -05:00
uint64_t before = g_get_monotonic_time ( ) ;
2020-01-22 13:15:45 -05:00
# endif
2008-06-02 17:41:35 -04:00
2022-01-20 15:39:13 -05:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Butler transport work, todo = [%1] (0x%3%4%5) at %2 \n " , enum_2_string ( ptw ) , before , std : : hex , ptw , std : : dec ) ) ;
2015-10-05 10:17:49 -04:00
2019-03-13 11:26:17 -04:00
if ( ptw & PostTransportAdjustPlaybackBuffering ) {
2019-12-11 22:36:00 -05:00
/* need to prevent concurrency with ARDOUR::Reader::run(),
* DiskWriter : : adjust_buffering ( ) re - allocates the ringbuffer */
2020-12-07 19:08:49 -05:00
Glib : : Threads : : Mutex : : Lock lx ( AudioEngine : : instance ( ) - > process_lock ( ) , Glib : : Threads : : NOT_LOCK ) ;
if ( ! have_process_lock ) {
lx . acquire ( ) ;
}
2023-04-07 17:33:13 -04:00
for ( auto const & i : * r ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2019-03-13 11:26:17 -04:00
if ( tr ) {
tr - > adjust_playback_buffering ( ) ;
/* and refill those buffers ... */
}
2023-04-07 17:33:13 -04:00
i - > non_realtime_locate ( _transport_sample ) ;
2019-03-13 11:26:17 -04:00
}
VCAList v = _vca_manager - > vcas ( ) ;
for ( VCAList : : const_iterator i = v . begin ( ) ; i ! = v . end ( ) ; + + i ) {
( * i ) - > non_realtime_locate ( _transport_sample ) ;
}
2008-06-02 17:41:35 -04:00
}
2019-03-13 11:26:17 -04:00
if ( ptw & PostTransportAdjustCaptureBuffering ) {
/* need to prevent concurrency with ARDOUR::DiskWriter::run(),
* DiskWriter : : adjust_buffering ( ) re - allocates the ringbuffer */
2020-12-07 19:08:49 -05:00
Glib : : Threads : : Mutex : : Lock lx ( AudioEngine : : instance ( ) - > process_lock ( ) , Glib : : Threads : : NOT_LOCK ) ;
if ( ! have_process_lock ) {
lx . acquire ( ) ;
}
2023-04-07 17:33:13 -04:00
for ( auto const & i : * r ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2019-03-13 11:26:17 -04:00
if ( tr ) {
tr - > adjust_capture_buffering ( ) ;
}
}
2014-10-22 12:18:31 -04:00
}
2023-02-17 02:31:21 -05:00
const int butler = _butler_seek_counter . load ( ) ;
const int rtlocates = _seek_counter . load ( ) ;
2022-03-17 19:41:08 -04:00
const bool will_locate = ( butler ! = rtlocates ) ;
2019-03-13 11:26:17 -04:00
if ( ptw & PostTransportStop ) {
2022-03-17 19:41:08 -04:00
non_realtime_stop ( ptw & PostTransportAbort , on_entry , finished , will_locate ) ;
2019-03-13 11:26:17 -04:00
if ( ! finished ) {
2023-02-17 02:31:21 -05:00
( void ) PBD : : atomic_dec_and_test ( _butler - > should_do_transport_work ) ;
2019-03-13 11:26:17 -04:00
goto restart ;
}
}
2008-06-02 17:41:35 -04:00
2021-07-10 20:29:21 -04:00
2022-03-17 19:41:08 -04:00
if ( will_locate ) {
2021-07-10 20:29:21 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " nonrealtime locate invoked from BTW (butler has done %1, rtlocs %2) \n " , butler , rtlocates ) ) ;
non_realtime_locate ( ) ;
}
2019-03-13 11:26:17 -04:00
if ( ptw & PostTransportOverWrite ) {
2020-05-13 20:51:18 -04:00
non_realtime_overwrite ( on_entry , finished , ( ptw & PostTransportLoopChanged ) ) ;
2019-03-13 11:26:17 -04:00
if ( ! finished ) {
2023-02-17 02:31:21 -05:00
( void ) PBD : : atomic_dec_and_test ( _butler - > should_do_transport_work ) ;
2019-03-13 11:26:17 -04:00
goto restart ;
2008-06-02 17:41:35 -04:00
}
2019-03-13 11:26:17 -04:00
}
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
if ( ptw & PostTransportAudition ) {
non_realtime_set_audition ( ) ;
2008-06-02 17:41:35 -04:00
}
2023-02-17 02:31:21 -05:00
( void ) PBD : : atomic_dec_and_test ( _butler - > should_do_transport_work ) ;
2019-03-13 11:26:17 -04:00
2020-01-21 23:50:18 -05:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( X_ ( " Butler transport work all done after %1 usecs @ %2 ptw %3 trw = %4 \n " ) , g_get_monotonic_time ( ) - before , _transport_sample , enum_2_string ( post_transport_work ( ) ) , _butler - > transport_work_requested ( ) ) ) ;
2017-09-28 00:31:12 -04:00
}
2019-03-13 11:26:17 -04:00
void
2020-05-13 20:51:18 -04:00
Session : : non_realtime_overwrite ( int on_entry , bool & finished , bool update_loop_declicks )
2008-09-10 11:03:30 -04:00
{
2020-05-13 20:51:18 -04:00
if ( update_loop_declicks ) {
DiskReader : : reset_loop_declick ( _locations - > auto_loop_location ( ) , sample_rate ( ) ) ;
}
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = routes . reader ( ) ;
for ( auto const & i : * rl ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2019-03-13 11:26:17 -04:00
if ( tr & & tr - > pending_overwrite ( ) ) {
tr - > overwrite_existing_buffers ( ) ;
}
2023-02-17 13:34:35 -05:00
if ( on_entry ! = _butler - > should_do_transport_work . load ( ) ) {
2019-03-13 11:26:17 -04:00
finished = false ;
return ;
2008-09-10 11:03:30 -04:00
}
}
2019-03-13 11:26:17 -04:00
}
2008-09-10 11:03:30 -04:00
2008-06-02 17:41:35 -04:00
void
2019-03-13 11:26:17 -04:00
Session : : non_realtime_locate ( )
2008-06-02 17:41:35 -04:00
{
2019-03-13 11:26:17 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " locate tracks to %1 \n " , _transport_sample ) ) ;
2015-09-28 17:42:02 -04:00
2019-03-13 11:26:17 -04:00
if ( Config - > get_loop_is_mode ( ) & & get_play_loop ( ) ) {
2015-09-28 17:42:02 -04:00
2019-03-13 11:26:17 -04:00
Location * loc = _locations - > auto_loop_location ( ) ;
2008-06-02 17:41:35 -04:00
2020-09-20 18:34:09 -04:00
if ( ! loc | | ( _transport_sample < loc - > start ( ) . samples ( ) | | _transport_sample > = loc - > end ( ) . samples ( ) ) ) {
2019-03-13 11:26:17 -04:00
/* jumped out of loop range: stop tracks from looping,
but leave loop ( mode ) enabled .
*/
set_track_loop ( false ) ;
2017-08-08 19:26:08 -04:00
2020-09-20 18:34:09 -04:00
} else if ( loc & & ( ( loc - > start ( ) . samples ( ) < = _transport_sample ) | | ( loc - > end ( ) . samples ( ) > _transport_sample ) ) ) {
2011-03-05 18:16:32 -05:00
2019-03-13 11:26:17 -04:00
/* jumping to start of loop. This might have been done before but it is
* idempotent and cheap . Doing it here ensures that when we start playback
* outside the loop we still flip tracks into the magic seamless mode
* when needed .
*/
set_track_loop ( true ) ;
2011-06-01 12:50:12 -04:00
2019-03-13 11:26:17 -04:00
} else if ( loc ) {
set_track_loop ( false ) ;
}
2015-10-05 10:17:49 -04:00
2011-05-30 17:37:58 -04:00
} else {
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
/* no more looping .. should have been noticed elsewhere */
}
2015-10-05 10:17:49 -04:00
2020-03-23 19:05:35 -04:00
microseconds_t start ;
uint32_t nt = 0 ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
samplepos_t tf ;
2020-11-12 20:38:24 -05:00
gint sc ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
{
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = routes . reader ( ) ;
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
restart :
2023-02-17 02:31:21 -05:00
sc = _seek_counter . load ( ) ;
2019-03-13 11:26:17 -04:00
tf = _transport_sample ;
2020-03-23 19:05:35 -04:00
start = get_microseconds ( ) ;
2008-08-04 18:37:24 -04:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * rl ) {
+ + nt ;
i - > non_realtime_locate ( tf ) ;
2023-02-17 02:31:21 -05:00
if ( sc ! = _seek_counter . load ( ) ) {
2019-03-13 11:26:17 -04:00
goto restart ;
2008-06-02 17:41:35 -04:00
}
}
2020-03-23 19:05:35 -04:00
microseconds_t end = get_microseconds ( ) ;
2023-04-07 17:33:13 -04:00
int usecs_per_track = lrintf ( ( end - start ) / std : : max < double > ( 1.0 , nt ) ) ;
2020-09-29 10:46:23 -04:00
# ifndef NDEBUG
2020-11-12 20:38:24 -05:00
std : : cerr < < " locate to " < < tf < < " took " < < ( end - start ) < < " usecs for " < < nt < < " tracks = " < < usecs_per_track < < " per track \n " ;
2020-09-29 10:46:23 -04:00
# endif
2023-02-17 02:31:21 -05:00
if ( usecs_per_track > _current_usecs_per_track . load ( ) ) {
2023-02-17 13:34:35 -05:00
_current_usecs_per_track . store ( usecs_per_track ) ;
2020-03-23 19:05:35 -04:00
}
2008-06-02 17:41:35 -04:00
}
2020-11-12 20:38:24 -05:00
/* we've caught up with whatever the _seek_counter was when we did the
non - realtime locates .
*/
2023-02-17 13:34:35 -05:00
_butler_seek_counter . store ( sc ) ;
2020-11-12 20:38:24 -05:00
2019-03-13 11:26:17 -04:00
{
/* VCAs are quick to locate because they have no data (except
automation ) associated with them . Don ' t bother with a
restart mechanism here , but do use the same transport sample
that the Routes used .
*/
VCAList v = _vca_manager - > vcas ( ) ;
for ( VCAList : : const_iterator i = v . begin ( ) ; i ! = v . end ( ) ; + + i ) {
( * i ) - > non_realtime_locate ( tf ) ;
2008-06-02 17:41:35 -04:00
}
}
2019-03-13 11:26:17 -04:00
_scene_changer - > locate ( _transport_sample ) ;
2012-06-22 10:27:51 -04:00
2019-03-13 11:26:17 -04:00
/* XXX: it would be nice to generate the new clicks here (in the non-RT thread)
rather than clearing them so that the RT thread has to spend time constructing
them ( in Session : : click ) .
*/
clear_clicks ( ) ;
}
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
bool
Session : : select_playhead_priority_target ( samplepos_t & jump_to )
{
if ( ! transport_master_no_external_or_using_engine ( ) | | ! config . get_auto_return ( ) ) {
return false ;
2010-06-28 13:25:40 -04:00
}
2019-03-13 11:26:17 -04:00
jump_to = _last_roll_location ;
return jump_to > = 0 ;
2008-06-02 17:41:35 -04:00
}
void
2019-03-13 11:26:17 -04:00
Session : : follow_playhead_priority ( )
2008-06-02 17:41:35 -04:00
{
2019-03-13 11:26:17 -04:00
samplepos_t target ;
2017-10-29 13:32:17 -04:00
2019-03-13 11:26:17 -04:00
if ( select_playhead_priority_target ( target ) ) {
request_locate ( target ) ;
2017-10-29 13:32:17 -04:00
}
2019-03-13 11:26:17 -04:00
}
2017-10-29 13:32:17 -04:00
2019-03-13 11:26:17 -04:00
void
2022-03-17 19:41:08 -04:00
Session : : non_realtime_stop ( bool abort , int on_entry , bool & finished , bool will_locate )
2019-03-13 11:26:17 -04:00
{
struct tm * now ;
time_t xnow ;
bool did_record ;
bool saved ;
PostTransportWork ptw = post_transport_work ( ) ;
2017-10-29 13:32:17 -04:00
2019-03-13 11:26:17 -04:00
did_record = false ;
saved = false ;
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = routes . reader ( ) ;
for ( auto const & i : * rl ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2019-03-13 11:26:17 -04:00
if ( tr & & tr - > get_captured_samples ( ) ! = 0 ) {
did_record = true ;
break ;
2012-07-26 11:40:11 -04:00
}
2008-06-02 17:41:35 -04:00
}
2019-03-13 11:26:17 -04:00
/* stop and locate are merged here because they share a lot of common stuff */
time ( & xnow ) ;
now = localtime ( & xnow ) ;
if ( auditioner ) {
auditioner - > cancel_audition ( ) ;
2011-08-09 17:17:55 -04:00
}
2022-01-21 15:08:47 -05:00
/* This must be called while _transport_sample still reflects where we stopped
*/
flush_cue_recording ( ) ;
2019-03-13 11:26:17 -04:00
if ( did_record ) {
begin_reversible_command ( Operations : : capture ) ;
_have_captured = true ;
}
2008-06-02 17:41:35 -04:00
2020-01-17 17:26:01 -05:00
DEBUG_TRACE ( DEBUG : : Transport , X_ ( " Butler post-transport-work, non realtime stop \n " ) ) ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
if ( abort & & did_record ) {
/* no reason to save the session file when we remove sources
*/
2019-03-18 10:33:05 -04:00
_state_of_the_state = StateOfTheState ( _state_of_the_state | InCleanup ) ;
2019-03-13 11:26:17 -04:00
}
2008-06-02 17:41:35 -04:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * rl ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2019-03-13 11:26:17 -04:00
if ( tr ) {
tr - > transport_stopped_wallclock ( * now , xnow , abort ) ;
2008-06-02 17:41:35 -04:00
}
2019-03-13 11:26:17 -04:00
}
2015-10-05 10:17:49 -04:00
2022-04-18 11:55:54 -04:00
pending_source_markers . clear ( ) ;
2019-03-13 11:26:17 -04:00
if ( abort & & did_record ) {
_state_of_the_state = StateOfTheState ( _state_of_the_state & ~ InCleanup ) ;
}
2014-10-10 13:22:45 -04:00
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > r = routes . reader ( ) ; /// why get another reader, and not use `rl' ?
2011-06-01 12:50:12 -04:00
2019-03-13 11:26:17 -04:00
if ( did_record ) {
commit_reversible_command ( ) ;
/* increase take name */
if ( config . get_track_name_take ( ) & & ! config . get_take_name ( ) . empty ( ) ) {
string newname = config . get_take_name ( ) ;
config . set_take_name ( bump_name_number ( newname ) ) ;
2015-12-13 12:16:57 -05:00
}
2019-03-13 11:26:17 -04:00
}
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
if ( _engine . running ( ) ) {
PostTransportWork ptw = post_transport_work ( ) ;
2014-04-17 09:47:06 -04:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * r ) {
i - > non_realtime_transport_stop ( _transport_sample , ! ( ptw & PostTransportLocate ) ) ;
2019-03-13 11:26:17 -04:00
}
VCAList v = _vca_manager - > vcas ( ) ;
for ( VCAList : : const_iterator i = v . begin ( ) ; i ! = v . end ( ) ; + + i ) {
( * i ) - > non_realtime_transport_stop ( _transport_sample , ! ( ptw & PostTransportLocate ) ) ;
}
}
2015-02-16 19:30:21 -05:00
2019-03-13 11:26:17 -04:00
/* If we are not synced to a "true" external master, and we're not
* handling an explicit locate , we should consider whether or not to
* " auto-return " . This could mean going to a specifically requested
* location , or just back to the start of the last roll .
*/
2015-02-16 19:30:21 -05:00
2020-01-23 16:25:01 -05:00
if ( transport_master_no_external_or_using_engine ( ) & & ! locate_initiated ( ) ) {
2015-02-16 19:30:21 -05:00
2019-03-13 11:26:17 -04:00
bool do_locate = false ;
2014-04-17 09:47:06 -04:00
2019-03-13 11:26:17 -04:00
if ( _requested_return_sample > = 0 ) {
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
/* explicit return request pre-queued in event list. overrides everything else */
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
_transport_sample = _requested_return_sample ;
2011-06-01 12:50:12 -04:00
2019-03-13 11:26:17 -04:00
/* cancel this request */
_requested_return_sample = - 1 ;
do_locate = true ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
} else if ( Config - > get_auto_return_target_list ( ) ) {
2017-09-28 00:31:12 -04:00
2019-03-13 11:26:17 -04:00
samplepos_t jump_to ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
if ( select_playhead_priority_target ( jump_to ) ) {
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
/* there's a valid target (we don't care how it
* was derived here )
*/
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
_transport_sample = jump_to ;
do_locate = true ;
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
} else if ( abort ) {
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
/* roll aborted (typically capture) with
* auto - return enabled
*/
2011-06-01 12:50:12 -04:00
2021-05-08 19:28:28 -04:00
if ( _last_roll_location > = 0 ) {
_transport_sample = _last_roll_location ;
do_locate = true ;
}
2019-03-13 11:26:17 -04:00
}
2008-06-02 17:41:35 -04:00
}
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
if ( do_locate & & synced_to_engine ( ) ) {
2012-06-22 10:45:44 -04:00
2019-03-13 11:26:17 -04:00
/* We will unconditionally locate to _transport_sample
* below , which will refill playback buffers based on
* _transport_sample , and maximises the buffering they
* represent .
*
* But if we are synced to engine ( JACK ) , we should
* locate the engine ( JACK ) as well . We would follow
* the engine ( JACK ) on the next process cycle , but
* since we ' re going to do a locate below anyway ,
* it seems pointless to not use just do it ourselves
* right now , rather than wait for the engine ( JACK ) to
* provide the new position on the next cycle .
*
* Despite the generic name of the called method
* ( : : transport_locate ( ) ) this method only does
* anything if the audio / MIDI backend is JACK .
*/
2011-06-01 12:50:12 -04:00
2019-03-13 11:26:17 -04:00
_engine . transport_locate ( _transport_sample ) ;
2015-03-10 05:46:24 -04:00
}
2008-06-02 17:41:35 -04:00
}
2019-03-13 11:26:17 -04:00
clear_clicks ( ) ;
unset_preroll_record_trim ( ) ;
2008-06-02 17:41:35 -04:00
2023-06-08 10:44:21 -04:00
/* do these before seeking, because otherwise the tracks will do the wrong thing in seamless loop mode. */
2019-03-13 11:26:17 -04:00
2023-06-08 10:44:21 -04:00
if ( ptw & PostTransportClearSubstate ) {
2019-03-13 11:26:17 -04:00
unset_play_range ( ) ;
2023-06-08 10:44:21 -04:00
}
if ( ptw & ( PostTransportClearSubstate | PostTransportStop ) ) {
2020-05-13 14:34:22 -04:00
if ( ! Config - > get_loop_is_mode ( ) & & get_play_loop ( ) & & ! loop_changing ) {
unset_play_loop ( ) ;
2019-03-13 11:26:17 -04:00
}
2008-06-02 17:41:35 -04:00
}
2008-08-04 18:37:24 -04:00
2020-05-17 11:08:49 -04:00
/* reset loop_changing so it does not affect next transport action */
loop_changing = false ;
2022-03-17 19:41:08 -04:00
if ( ! will_locate & & ! _transport_fsm - > declicking_for_locate ( ) ) {
2020-01-23 16:25:01 -05:00
2019-03-13 11:26:17 -04:00
DEBUG_TRACE ( DEBUG : : Transport , X_ ( " Butler PTW: locate \n " ) ) ;
2020-01-23 16:25:01 -05:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * r ) {
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " Butler PTW: locate on %1 \n " , i - > name ( ) ) ) ;
i - > non_realtime_locate ( _transport_sample ) ;
2008-06-02 17:41:35 -04:00
2023-02-17 13:34:35 -05:00
if ( on_entry ! = _butler - > should_do_transport_work . load ( ) ) {
2019-03-13 11:26:17 -04:00
finished = false ;
/* we will be back */
return ;
}
}
2011-05-11 20:17:51 -04:00
2019-03-13 11:26:17 -04:00
VCAList v = _vca_manager - > vcas ( ) ;
for ( VCAList : : const_iterator i = v . begin ( ) ; i ! = v . end ( ) ; + + i ) {
( * i ) - > non_realtime_locate ( _transport_sample ) ;
}
}
2011-06-01 12:50:12 -04:00
2008-12-12 09:43:24 -05:00
have_looped = false ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
/* don't bother with this stuff if we're disconnected from the engine,
because there will be no process callbacks to deliver stuff from
2008-06-02 17:41:35 -04:00
*/
2019-03-13 11:26:17 -04:00
if ( _engine . running ( ) & & ! _engine . freewheeling ( ) ) {
// need to queue this in the next RT cycle
_send_timecode_update = true ;
2008-06-02 17:41:35 -04:00
2020-01-27 15:49:53 -05:00
if ( transport_master ( ) - > type ( ) ! = MTC ) { // why?
2019-03-13 11:26:17 -04:00
send_immediate_mmc ( MIDI : : MachineControlCommand ( MIDI : : MachineControl : : cmdStop ) ) ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
/* This (::non_realtime_stop()) gets called by main
process thread , which will lead to confusion
when calling AsyncMIDIPort : : write ( ) .
2008-08-04 18:37:24 -04:00
2019-03-13 11:26:17 -04:00
Something must be done . XXX
*/
send_mmc_locate ( _transport_sample ) ;
2012-02-06 12:09:53 -05:00
}
2019-03-13 11:26:17 -04:00
}
2017-01-17 14:43:55 -05:00
2019-03-13 11:26:17 -04:00
if ( ( ptw & PostTransportLocate ) & & get_record_enabled ( ) ) {
/* This is scheduled by realtime_stop(), which is also done
* when a slave requests / locate / for an initial sync .
* We can ' t hold up the slave for long with a save ( ) here ,
* without breaking its initial sync cycle .
*
* save state only if there ' s no slave or if it ' s not yet locked .
*/
if ( ! transport_master_is_external ( ) | | ! transport_master ( ) - > locked ( ) ) {
DEBUG_TRACE ( DEBUG : : Transport , X_ ( " Butler PTW: requests save \n " ) ) ;
SaveSessionRequested ( _current_snapshot_name ) ;
saved = true ;
}
}
2017-01-17 14:43:55 -05:00
2019-03-13 11:26:17 -04:00
/* save the current state of things if appropriate */
2017-09-28 00:31:12 -04:00
2019-03-13 11:26:17 -04:00
if ( did_record & & ! saved ) {
SaveSessionRequested ( _current_snapshot_name ) ;
}
2017-09-28 00:31:12 -04:00
2019-03-13 11:26:17 -04:00
PositionChanged ( _transport_sample ) ; /* EMIT SIGNAL */
2021-05-03 19:35:34 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " send TSC with speed = %1 \n " , _transport_fsm - > transport_speed ( ) ) ) ;
2008-06-02 17:41:35 -04:00
TransportStateChange ( ) ; /* EMIT SIGNAL */
2019-03-13 11:26:17 -04:00
AutomationWatch : : instance ( ) . transport_stop_automation_watches ( _transport_sample ) ;
2008-06-02 17:41:35 -04:00
}
void
2019-12-05 15:00:24 -05:00
Session : : set_play_loop ( bool yn , bool change_transport_state )
{
ENSURE_PROCESS_THREAD ;
/* Called from event-handling context */
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " set_play_loop (%1) \n " , yn ) ) ;
Location * loc ;
2020-02-26 14:15:00 -05:00
if ( yn = = get_play_loop ( ) | | ( actively_recording ( ) & & yn ) | | ( loc = _locations - > auto_loop_location ( ) ) = = 0 ) {
2019-12-05 15:00:24 -05:00
/* nothing to do, or can't change loop status while recording */
return ;
}
if ( yn & & synced_to_engine ( ) ) {
warning < < string_compose (
_ ( " Looping cannot be supported while %1 is using JACK transport. \n "
" Recommend changing the configured options " ) , PROGRAM_NAME )
< < endmsg ;
return ;
}
2020-02-26 20:36:16 -05:00
if ( yn & & ! maybe_allow_only_loop ( true ) ) {
return ;
}
2019-12-05 15:00:24 -05:00
if ( yn ) {
play_loop = true ;
have_looped = false ;
2020-02-26 14:15:00 -05:00
unset_play_range ( ) ;
/* set all tracks to use internal looping */
set_track_loop ( true ) ;
2019-12-05 15:00:24 -05:00
2020-09-20 18:34:09 -04:00
merge_event ( new SessionEvent ( SessionEvent : : AutoLoop , SessionEvent : : Replace , loc - > end ( ) . samples ( ) , loc - > start ( ) . samples ( ) , 0.0f ) ) ;
2019-12-05 15:00:24 -05:00
2020-02-26 14:15:00 -05:00
if ( ! Config - > get_loop_is_mode ( ) ) {
2020-05-17 11:08:49 -04:00
if ( transport_rolling ( ) ) {
/* set loop_changing to ensure that non_realtime_stop does not unset_play_loop */
loop_changing = true ;
}
2021-04-28 21:54:51 -04:00
/* args: position, disposition, for_loop_end=false, force=true */
2020-09-20 18:34:09 -04:00
TFSM_LOCATE ( loc - > start ( ) . samples ( ) , MustRoll , false , true ) ;
2020-05-12 14:45:39 -04:00
} else {
if ( ! transport_rolling ( ) ) {
/* loop-is-mode: not rolling, just locate to loop start */
2020-09-20 18:34:09 -04:00
TFSM_LOCATE ( loc - > start ( ) . samples ( ) , MustStop , false , true ) ;
2020-05-12 14:45:39 -04:00
}
2019-12-05 15:00:24 -05:00
}
2020-02-26 14:15:00 -05:00
TransportStateChange ( ) ; /* EMIT SIGNAL */
2019-12-05 15:00:24 -05:00
} else {
unset_play_loop ( ) ;
}
2021-05-03 19:35:34 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " send TSC2 with speed = %1 \n " , _transport_fsm - > transport_speed ( ) ) ) ;
2019-12-05 15:00:24 -05:00
}
void
Session : : unset_play_loop ( bool change_transport_state )
2008-06-02 17:41:35 -04:00
{
2020-02-26 14:15:00 -05:00
if ( ! get_play_loop ( ) ) {
return ;
}
2019-12-05 15:00:24 -05:00
2020-02-26 14:15:00 -05:00
play_loop = false ;
clear_events ( SessionEvent : : AutoLoop ) ;
set_track_loop ( false ) ;
2019-12-05 15:00:24 -05:00
2020-02-26 14:15:00 -05:00
/* likely need to flush track buffers: this will locate us to wherever we are */
2019-12-05 15:00:24 -05:00
2020-02-26 14:15:00 -05:00
if ( change_transport_state & & transport_rolling ( ) ) {
TFSM_STOP ( false , false ) ;
2008-06-02 17:41:35 -04:00
}
2020-02-26 14:15:00 -05:00
2023-02-16 18:33:28 -05:00
overwrite_some_buffers ( std : : shared_ptr < Route > ( ) , LoopDisabled ) ;
2020-02-26 14:15:00 -05:00
TransportStateChange ( ) ; /* EMIT SIGNAL */
2019-03-13 11:26:17 -04:00
}
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
void
Session : : set_track_loop ( bool yn )
{
Location * loc = _locations - > auto_loop_location ( ) ;
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
if ( ! loc ) {
yn = false ;
2008-06-02 17:41:35 -04:00
}
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = routes . reader ( ) ;
2008-06-02 17:41:35 -04:00
2023-04-07 17:33:13 -04:00
for ( auto const & i : * rl ) {
if ( ! i - > is_private_route ( ) ) {
i - > set_loop ( yn ? loc : 0 ) ;
2008-06-02 17:41:35 -04:00
}
}
2019-11-23 01:41:56 -05:00
DiskReader : : reset_loop_declick ( loc , nominal_sample_rate ( ) ) ;
2019-03-13 11:26:17 -04:00
}
2008-06-02 17:41:35 -04:00
2019-03-13 11:26:17 -04:00
samplecnt_t
Session : : worst_latency_preroll ( ) const
{
return _worst_output_latency + _worst_input_latency ;
2008-06-02 17:41:35 -04:00
}
2019-12-27 23:11:38 -05:00
samplecnt_t
Session : : worst_latency_preroll_buffer_size_ceil ( ) const
{
return lrintf ( ceil ( ( _worst_output_latency + _worst_input_latency ) / ( float ) current_block_size ) * current_block_size ) ;
}
2008-06-02 17:41:35 -04:00
void
2009-11-08 11:28:21 -05:00
Session : : unset_play_range ( )
2008-06-02 17:41:35 -04:00
{
2009-11-08 11:28:21 -05:00
_play_range = false ;
2009-12-03 21:15:12 -05:00
_clear_event_type ( SessionEvent : : RangeStop ) ;
_clear_event_type ( SessionEvent : : RangeLocate ) ;
2008-06-02 17:41:35 -04:00
}
void
2020-09-20 18:34:09 -04:00
Session : : set_play_range ( list < TimelineRange > & range , bool leave_rolling )
2008-06-02 17:41:35 -04:00
{
2009-12-03 21:15:12 -05:00
SessionEvent * ev ;
2008-08-04 18:37:24 -04:00
2009-11-08 11:28:21 -05:00
/* Called from event-processing context */
2008-06-02 17:41:35 -04:00
2009-11-08 11:28:21 -05:00
unset_play_range ( ) ;
2011-06-01 12:50:12 -04:00
2009-11-08 11:28:21 -05:00
if ( range . empty ( ) ) {
/* _play_range set to false in unset_play_range()
*/
if ( ! leave_rolling ) {
/* stop transport */
2009-12-03 21:15:12 -05:00
SessionEvent * ev = new SessionEvent ( SessionEvent : : SetTransportSpeed , SessionEvent : : Add , SessionEvent : : Immediate , 0 , 0.0f , false ) ;
2009-11-08 11:28:21 -05:00
merge_event ( ev ) ;
}
2008-06-02 17:41:35 -04:00
return ;
}
2009-11-08 11:28:21 -05:00
_play_range = true ;
2008-08-04 18:37:24 -04:00
2009-11-08 11:28:21 -05:00
/* cancel loop play */
unset_play_loop ( ) ;
2008-08-04 18:37:24 -04:00
2020-09-20 18:34:09 -04:00
list < TimelineRange > : : size_type sz = range . size ( ) ;
2011-06-01 12:50:12 -04:00
2009-11-08 11:28:21 -05:00
if ( sz > 1 ) {
2011-06-01 12:50:12 -04:00
2020-09-20 18:34:09 -04:00
list < TimelineRange > : : iterator i = range . begin ( ) ;
list < TimelineRange > : : iterator next ;
2011-06-01 12:50:12 -04:00
2009-11-08 11:28:21 -05:00
while ( i ! = range . end ( ) ) {
2011-06-01 12:50:12 -04:00
2008-06-02 17:41:35 -04:00
next = i ;
+ + next ;
2011-06-01 12:50:12 -04:00
2008-06-02 17:41:35 -04:00
/* locating/stopping is subject to delays for declicking.
*/
2011-06-01 12:50:12 -04:00
2020-09-20 18:34:09 -04:00
samplepos_t requested_sample = i - > end ( ) . samples ( ) ;
2011-06-01 12:50:12 -04:00
2017-09-18 12:39:17 -04:00
if ( requested_sample > current_block_size ) {
requested_sample - = current_block_size ;
2008-06-02 17:41:35 -04:00
} else {
2017-09-18 12:39:17 -04:00
requested_sample = 0 ;
2008-06-02 17:41:35 -04:00
}
2011-06-01 12:50:12 -04:00
2009-11-08 11:28:21 -05:00
if ( next = = range . end ( ) ) {
2017-09-18 12:39:17 -04:00
ev = new SessionEvent ( SessionEvent : : RangeStop , SessionEvent : : Add , requested_sample , 0 , 0.0f ) ;
2008-06-02 17:41:35 -04:00
} else {
2020-09-20 18:34:09 -04:00
ev = new SessionEvent ( SessionEvent : : RangeLocate , SessionEvent : : Add , requested_sample , ( * next ) . start ( ) . samples ( ) , 0.0f ) ;
2008-06-02 17:41:35 -04:00
}
2011-06-01 12:50:12 -04:00
2008-06-02 17:41:35 -04:00
merge_event ( ev ) ;
2011-06-01 12:50:12 -04:00
2008-06-02 17:41:35 -04:00
i = next ;
}
2011-06-01 12:50:12 -04:00
2008-06-02 17:41:35 -04:00
} else if ( sz = = 1 ) {
2008-08-04 18:37:24 -04:00
2020-09-20 18:34:09 -04:00
ev = new SessionEvent ( SessionEvent : : RangeStop , SessionEvent : : Add , range . front ( ) . end ( ) . samples ( ) , 0 , 0.0f ) ;
2008-06-02 17:41:35 -04:00
merge_event ( ev ) ;
2011-06-01 12:50:12 -04:00
}
2008-08-04 18:37:24 -04:00
2009-11-08 11:28:21 -05:00
/* save range so we can do auto-return etc. */
current_audio_range = range ;
2008-06-02 17:41:35 -04:00
/* now start rolling at the right place */
2008-08-04 18:37:24 -04:00
2020-09-20 18:34:09 -04:00
ev = new SessionEvent ( SessionEvent : : LocateRoll , SessionEvent : : Add , SessionEvent : : Immediate , range . front ( ) . start ( ) . samples ( ) , 0.0f , false ) ;
2008-06-02 17:41:35 -04:00
merge_event ( ev ) ;
2011-06-01 12:50:12 -04:00
2021-05-03 19:35:34 -04:00
DEBUG_TRACE ( DEBUG : : Transport , string_compose ( " send TSC5 with speed = %1 \n " , _transport_fsm - > transport_speed ( ) ) ) ;
2019-12-29 20:41:23 -05:00
TransportStateChange ( ) ; /* EMIT SIGNAL */
2008-06-02 17:41:35 -04:00
}
void
2017-09-18 12:39:17 -04:00
Session : : request_bounded_roll ( samplepos_t start , samplepos_t end )
2008-06-02 17:41:35 -04:00
{
2020-09-20 18:34:09 -04:00
TimelineRange ar ( timepos_t ( start ) , timepos_t ( end ) , 0 ) ;
list < TimelineRange > lar ;
2008-06-02 17:41:35 -04:00
2009-11-08 11:28:21 -05:00
lar . push_back ( ar ) ;
request_play_range ( & lar , true ) ;
}
2014-07-03 13:25:35 -04:00
void
2017-09-18 12:39:17 -04:00
Session : : set_requested_return_sample ( samplepos_t return_to )
2014-07-03 13:25:35 -04:00
{
2017-09-18 12:39:17 -04:00
_requested_return_sample = return_to ;
2014-07-03 13:25:35 -04:00
}
2008-06-02 17:41:35 -04:00
void
2017-09-18 12:39:17 -04:00
Session : : request_roll_at_and_return ( samplepos_t start , samplepos_t return_to )
2008-06-02 17:41:35 -04:00
{
2021-06-22 14:03:32 -04:00
SessionEvent * ev = new SessionEvent ( SessionEvent : : LocateRollLocate , SessionEvent : : Add , SessionEvent : : Immediate , return_to , _transport_fsm - > default_speed ( ) ) ;
2017-09-18 12:39:17 -04:00
ev - > target2_sample = start ;
2008-06-02 17:41:35 -04:00
queue_event ( ev ) ;
}
void
Session : : engine_halted ( )
{
/* there will be no more calls to process(), so
we ' d better clean up for ourselves , right now .
2019-09-17 20:26:03 -04:00
We can ' t queue SessionEvents because they only get
handled from within a process callback .
2008-06-02 17:41:35 -04:00
*/
2022-01-28 00:41:37 -05:00
cancel_audition ( ) ;
2008-06-02 17:41:35 -04:00
2019-09-17 20:26:03 -04:00
/* 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 )
*/
2008-08-04 18:37:24 -04:00
2019-09-20 00:33:43 -04:00
_transport_fsm - > stop ( ) ;
2008-06-02 17:41:35 -04:00
2019-09-17 20:26:03 -04:00
/* 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 ) ;
2008-06-02 17:41:35 -04:00
}
2019-09-17 20:26:03 -04:00
void
Session : : engine_running ( )
{
2019-09-20 00:33:43 -04:00
_transport_fsm - > start ( ) ;
2020-02-24 16:21:18 -05:00
reset_xrun_count ( ) ;
2019-09-17 20:26:03 -04:00
}
2008-06-02 17:41:35 -04:00
void
Session : : xrun_recovery ( )
{
2015-04-28 21:09:17 -04:00
+ + _xrun_count ;
2017-09-18 12:39:17 -04:00
Xrun ( _transport_sample ) ; /* EMIT SIGNAL */
2008-06-02 17:41:35 -04:00
2021-02-12 18:20:30 -05:00
if ( actively_recording ( ) ) {
2021-02-05 16:18:21 -05:00
+ + _capture_xruns ;
2021-02-12 18:20:30 -05:00
if ( Config - > get_stop_recording_on_xrun ( ) ) {
2008-06-02 17:41:35 -04:00
2021-02-12 18:20:30 -05:00
/* it didn't actually halt, but we need
* to handle things in the same way .
*/
engine_halted ( ) ;
2008-06-02 17:41:35 -04:00
2021-02-12 18:20:30 -05:00
/* ..and start the FSM engine again */
_transport_fsm - > start ( ) ;
} else {
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = routes . reader ( ) ;
for ( auto const & i : * rl ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2021-02-12 18:20:30 -05:00
if ( tr ) {
tr - > mark_capture_xrun ( ) ;
}
}
2020-04-01 21:58:21 -04:00
2021-02-12 18:20:30 -05:00
}
2008-08-04 18:37:24 -04:00
}
2021-05-28 15:22:10 -04:00
else if ( _exporting & & _realtime_export & & _export_rolling ) {
+ + _export_xruns ;
2021-04-29 11:50:28 -04:00
}
2008-06-02 17:41:35 -04:00
}
2021-06-13 20:48:50 -04:00
void
Session : : reset_xrun_count ( )
{
_xrun_count = 0 ;
2021-06-13 21:14:07 -04:00
ARDOUR : : reset_performance_meters ( this ) ;
2021-06-13 20:48:50 -04:00
Xrun ( - 1 ) ; /* EMIT SIGNAL */
}
2009-11-30 18:16:28 -05:00
void
Session : : route_processors_changed ( RouteProcessorChange c )
{
2023-02-17 02:31:21 -05:00
if ( _ignore_route_processor_changes . load ( ) > 0 ) {
( void ) _ignored_a_processor_change . fetch_or ( c . type ) ;
2012-01-17 20:30:44 -05:00
return ;
}
2009-11-30 18:16:28 -05:00
if ( c . type = = RouteProcessorChange : : MeterPointChange ) {
2022-05-23 20:13:54 -04:00
/* sort rec-armed routes first */
resort_routes ( ) ;
2015-04-26 23:01:07 -04:00
set_dirty ( ) ;
return ;
}
if ( c . type = = RouteProcessorChange : : RealTimeChange ) {
set_dirty ( ) ;
2009-11-30 18:16:28 -05:00
return ;
}
2010-04-06 08:13:38 -04:00
resort_routes ( ) ;
2023-01-27 17:04:15 -05:00
if ( c . type = = RouteProcessorChange : : SendReturnChange ) {
update_latency_compensation ( true , false ) ;
} else {
update_latency_compensation ( false , false ) ;
}
2011-04-21 11:53:53 -04:00
set_dirty ( ) ;
2009-11-30 18:16:28 -05:00
}
2008-06-02 17:41:35 -04:00
void
Session : : allow_auto_play ( bool yn )
{
auto_play_legal = yn ;
}
2010-07-04 21:13:36 -04:00
void
2017-09-18 12:39:17 -04:00
Session : : send_mmc_locate ( samplepos_t t )
2010-07-04 21:13:36 -04:00
{
2014-10-22 12:18:31 -04:00
if ( t < 0 ) {
return ;
}
2012-02-06 12:09:53 -05:00
if ( ! _engine . freewheeling ( ) ) {
Timecode : : Time time ;
timecode_time_subframes ( t , time ) ;
2014-10-22 17:06:53 -04:00
send_immediate_mmc ( MIDI : : MachineControlCommand ( time ) ) ;
2012-02-06 12:09:53 -05:00
}
2010-07-04 21:13:36 -04:00
}
2010-08-01 21:59:34 -04:00
/** Ask the transport to not send timecode until further notice. The suspension
* will come into effect some finite time after this call , and timecode_transmission_suspended ( )
* should be checked by the caller to find out when .
*/
void
Session : : request_suspend_timecode_transmission ( )
{
SessionEvent * ev = new SessionEvent ( SessionEvent : : SetTimecodeTransmission , SessionEvent : : Add , SessionEvent : : Immediate , 0 , 0 , false ) ;
queue_event ( ev ) ;
}
void
Session : : request_resume_timecode_transmission ( )
{
SessionEvent * ev = new SessionEvent ( SessionEvent : : SetTimecodeTransmission , SessionEvent : : Add , SessionEvent : : Immediate , 0 , 0 , true ) ;
queue_event ( ev ) ;
}
bool
Session : : timecode_transmission_suspended ( ) const
{
2023-02-17 02:31:21 -05:00
return _suspend_timecode_transmission . load ( ) = = 1 ;
2010-08-01 21:59:34 -04:00
}
2018-09-18 18:51:59 -04:00
2023-02-16 18:33:28 -05:00
std : : shared_ptr < TransportMaster >
2018-09-18 18:51:59 -04:00
Session : : transport_master ( ) const
{
return TransportMasterManager : : instance ( ) . current ( ) ;
}
bool
Session : : transport_master_is_external ( ) const
{
2019-01-25 00:05:20 -05:00
return TransportMasterManager : : instance ( ) . current ( ) & & config . get_external_sync ( ) ;
2018-09-18 18:51:59 -04:00
}
2019-02-28 19:18:08 -05:00
bool
Session : : transport_master_no_external_or_using_engine ( ) const
{
return ! TransportMasterManager : : instance ( ) . current ( ) | | ! config . get_external_sync ( ) | | ( TransportMasterManager : : instance ( ) . current ( ) - > type ( ) = = Engine ) ;
}
2018-09-18 18:51:59 -04:00
void
Session : : sync_source_changed ( SyncSource type , samplepos_t pos , pframes_t cycle_nframes )
{
/* Runs in process() context */
2023-02-16 18:33:28 -05:00
std : : shared_ptr < TransportMaster > master = TransportMasterManager : : instance ( ) . current ( ) ;
2018-09-18 18:51:59 -04:00
if ( master - > can_loop ( ) ) {
request_play_loop ( false ) ;
} else if ( master - > has_loop ( ) ) {
request_play_loop ( true ) ;
}
/* slave change, reset any DiskIO block on disk output because it is no
longer valid with a new slave .
*/
2020-03-13 15:49:44 -04:00
TransportMasterManager : : instance ( ) . unblock_disk_output ( ) ;
2018-09-18 18:51:59 -04:00
#if 0
we should not be treating specific transport masters as special cases because there maybe > 1 of a particular type
2023-02-16 18:33:28 -05:00
std : : shared_ptr < MTC_TransportMaster > mtc_master = std : : dynamic_pointer_cast < MTC_TransportMaster > ( master ) ;
2018-09-18 18:51:59 -04:00
if ( mtc_master ) {
mtc_master - > ActiveChanged . connect_same_thread ( mtc_status_connection , boost : : bind ( & Session : : mtc_status_changed , this , _1 ) ) ;
MTCSyncStateChanged ( mtc_master - > locked ( ) ) ;
} else {
2023-02-17 13:34:35 -05:00
int canderef ( 1 ) ;
if ( _mtc_active . compare_exchange_strong ( canderef , 0 ) ) {
2018-09-18 18:51:59 -04:00
MTCSyncStateChanged ( false ) ;
}
mtc_status_connection . disconnect ( ) ;
}
2023-02-16 18:33:28 -05:00
std : : shared_ptr < LTC_TransportMaster > ltc_master = std : : dynamic_pointer_cast < LTC_TransportMaster > ( master ) ;
2018-09-18 18:51:59 -04:00
if ( ltc_master ) {
ltc_master - > ActiveChanged . connect_same_thread ( ltc_status_connection , boost : : bind ( & Session : : ltc_status_changed , this , _1 ) ) ;
LTCSyncStateChanged ( ltc_master - > locked ( ) ) ;
} else {
2023-02-17 13:34:35 -05:00
int canderef ( 1 ) ;
if ( _ltc_active . compare_exchange_strong ( canderef , 0 ) ) {
2018-09-18 18:51:59 -04:00
LTCSyncStateChanged ( false ) ;
}
ltc_status_connection . disconnect ( ) ;
}
# endif
DEBUG_TRACE ( DEBUG : : Slave , string_compose ( " set new slave to %1 \n " , master ) ) ;
// need to queue this for next process() cycle
_send_timecode_update = true ;
2023-04-07 17:33:13 -04:00
std : : shared_ptr < RouteList const > rl = routes . reader ( ) ;
2018-09-18 18:51:59 -04:00
const bool externally_slaved = transport_master_is_external ( ) ;
2023-04-07 17:33:13 -04:00
for ( auto const & i : * rl ) {
std : : shared_ptr < Track > tr = std : : dynamic_pointer_cast < Track > ( i ) ;
2018-09-18 18:51:59 -04:00
if ( tr & & ! tr - > is_private_route ( ) ) {
tr - > set_slaved ( externally_slaved ) ;
}
}
set_dirty ( ) ;
}
2019-09-17 20:26:03 -04:00
bool
Session : : transport_stopped ( ) const
{
return _transport_fsm - > stopped ( ) ;
}
2019-12-29 20:41:02 -05:00
bool
Session : : transport_stopped_or_stopping ( ) const
{
return _transport_fsm - > stopped ( ) | | _transport_fsm - > stopping ( ) ;
}
2020-06-11 23:02:06 -04:00
bool
Session : : transport_state_rolling ( ) const
{
return _transport_fsm - > rolling ( ) ;
}
2022-03-17 14:17:01 -04:00
bool
Session : : transport_locating ( ) const
{
return _transport_fsm - > locating ( ) ;
}
2019-09-17 20:26:03 -04:00
bool
Session : : transport_rolling ( ) const
{
2021-05-03 19:35:34 -04:00
return _transport_fsm - > transport_speed ( ) ! = 0.0 & & _count_in_samples = = 0 & & _remaining_latency_preroll = = 0 ;
2019-09-17 20:26:03 -04:00
}
bool
Session : : locate_pending ( ) const
{
return _transport_fsm - > locating ( ) ;
}
2020-01-23 16:24:03 -05:00
bool
Session : : locate_initiated ( ) const
{
return _transport_fsm - > declicking_for_locate ( ) | | _transport_fsm - > locating ( ) ;
}
2019-09-17 20:26:03 -04:00
bool
Session : : declick_in_progress ( ) const
{
return _transport_fsm - > declick_in_progress ( ) ;
}
2020-02-20 02:25:25 -05:00
bool
Session : : transport_will_roll_forwards ( ) const
{
2022-05-12 10:15:15 -04:00
return _transport_fsm - > will_roll_forwards ( ) ;
2020-02-20 02:25:25 -05:00
}
2021-05-03 19:35:34 -04:00
double
Session : : transport_speed ( ) const
{
if ( _transport_fsm - > transport_speed ( ) ! = _transport_fsm - > transport_speed ( ) ) {
// cerr << "\n\n!!TS " << _transport_fsm->transport_speed() << " TFSM::speed " << _transport_fsm->transport_speed() << " via " << _transport_fsm->current_state() << endl;
}
return _count_in_samples > 0 ? 0. : _transport_fsm - > transport_speed ( ) ;
}
double
Session : : actual_speed ( ) const
{
if ( _transport_fsm - > transport_speed ( ) > 0 ) return _engine_speed ;
if ( _transport_fsm - > transport_speed ( ) < 0 ) return - _engine_speed ;
return 0 ;
}
2022-01-21 12:24:03 -05:00
void
Session : : flush_cue_recording ( )
{
2022-01-23 21:15:12 -05:00
/* if the user canceled cue recording before stopping *and* didn't record any cues, leave cues unchanged */
if ( ! TriggerBox : : cue_recording ( ) & & ! TriggerBox : : cue_records . read_space ( ) ) {
2022-01-21 15:08:47 -05:00
return ;
}
2022-01-21 12:24:03 -05:00
CueRecord cr ;
TempoMap : : SharedPtr tmap ( TempoMap : : use ( ) ) ;
2022-01-23 21:15:12 -05:00
/* we will delete the cues we rolled over, even if the user never wrote any new cues (??)*/
2022-01-21 15:08:47 -05:00
_locations - > clear_cue_markers ( _last_roll_location , _transport_sample ) ;
2022-01-21 12:24:03 -05:00
while ( TriggerBox : : cue_records . read ( & cr , 1 ) = = 1 ) {
2023-02-12 14:02:22 -05:00
BBT_Argument bbt = tmap - > bbt_at ( timepos_t ( cr . when ) ) ;
bbt = BBT_Argument ( bbt . reference ( ) , bbt . round_up_to_bar ( ) ) ;
2022-01-21 12:24:03 -05:00
2022-05-23 16:24:34 -04:00
const timepos_t when ( tmap - > quarters_at ( bbt ) ) ;
2022-01-21 12:24:03 -05:00
Location * l = new Location ( * this , when , when , std : : string ( ) , Location : : Flags ( Location : : IsMark | Location : : IsCueMarker ) , cr . cue_number ) ;
_locations - > add ( l ) ;
}
2022-01-21 12:31:16 -05:00
/* scheduled sync of cue markers in RT thread */
cue_marker_change ( 0 ) ;
2022-01-23 21:15:12 -05:00
/* disarm the cues from recording when we finish our pass */
TriggerBox : : set_cue_recording ( false ) ;
2022-01-21 12:24:03 -05:00
}