2012-07-13 21:13:35 +00:00
/*
2019-08-03 04:01:25 +02:00
* Copyright ( C ) 2012 - 2017 Paul Davis < paul @ linuxaudiosystems . com >
* Copyright ( C ) 2015 - 2018 Robin Gareus < robin @ gareus . org >
* Copyright ( C ) 2015 Ben Loftis < ben @ harrisonconsoles . com >
* Copyright ( C ) 2015 Nick Mainsbridge < mainsbridge @ gmail . 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 .
*/
2012-07-13 21:13:35 +00:00
# include <iostream>
2013-07-11 15:10:10 -04:00
# include <glibmm/timer.h>
2012-07-13 21:13:35 +00:00
# include "pbd/compose.h"
2017-08-29 17:16:03 +02:00
# include "pbd/pthread_utils.h"
2012-07-13 21:13:35 +00:00
2021-01-14 18:34:43 -07:00
# include "temporal/tempo.h"
2018-10-11 01:36:49 +02:00
# include "ardour/audioengine.h"
2012-07-13 21:13:35 +00:00
# include "ardour/automation_control.h"
# include "ardour/automation_watch.h"
# include "ardour/debug.h"
# include "ardour/session.h"
using namespace ARDOUR ;
using namespace PBD ;
AutomationWatch * AutomationWatch : : _instance = 0 ;
AutomationWatch &
AutomationWatch : : instance ( )
{
if ( _instance = = 0 ) {
_instance = new AutomationWatch ;
}
return * _instance ;
}
AutomationWatch : : AutomationWatch ( )
: _thread ( 0 )
2015-03-25 14:28:25 -05:00
, _last_time ( 0 )
2012-07-13 21:13:35 +00:00
, _run_thread ( false )
{
2015-10-05 16:17:49 +02:00
2012-07-13 21:13:35 +00:00
}
AutomationWatch : : ~ AutomationWatch ( )
{
if ( _thread ) {
_run_thread = false ;
_thread - > join ( ) ;
_thread = 0 ;
}
2012-07-25 17:48:55 +00:00
Glib : : Threads : : Mutex : : Lock lm ( automation_watch_lock ) ;
2012-07-13 21:13:35 +00:00
automation_watches . clear ( ) ;
2017-07-22 15:40:27 +02:00
automation_connections . clear ( ) ;
2012-07-13 21:13:35 +00:00
}
void
2023-02-16 16:33:28 -07:00
AutomationWatch : : add_automation_watch ( std : : shared_ptr < AutomationControl > ac )
2012-07-13 21:13:35 +00:00
{
2012-07-25 17:48:55 +00:00
Glib : : Threads : : Mutex : : Lock lm ( automation_watch_lock ) ;
2012-11-14 15:06:41 +00:00
DEBUG_TRACE ( DEBUG : : Automation , string_compose ( " now watching control %1 for automation, astate = %2 \n " , ac - > name ( ) , enum_2_string ( ac - > automation_state ( ) ) ) ) ;
2017-07-22 15:40:27 +02:00
std : : pair < AutomationWatches : : iterator , bool > r = automation_watches . insert ( ac ) ;
if ( ! r . second ) {
return ;
}
2012-07-13 21:13:35 +00:00
2012-07-17 03:10:40 +00:00
/* if an automation control is added here while the transport is
* rolling , make sure that it knows that there is a write pass going
* on , rather than waiting for the transport to start .
*/
if ( _session & & _session - > transport_rolling ( ) & & ac - > alist ( ) - > automation_write ( ) ) {
2015-10-04 14:51:05 -04:00
DEBUG_TRACE ( DEBUG : : Automation , string_compose ( " \t transport is rolling @ %1, audible = %2so enter write pass \n " ,
2017-09-18 12:39:17 -04:00
_session - > transport_speed ( ) , _session - > audible_sample ( ) ) ) ;
2013-04-02 16:10:51 -04:00
/* add a guard point since we are already moving */
2021-01-14 18:34:43 -07:00
timepos_t pos ;
if ( ac - > list ( ) - > time_domain ( ) = = Temporal : : AudioTime ) {
pos = timepos_t ( _session - > audible_sample ( ) ) ;
} else {
pos = timepos_t ( Temporal : : TempoMap : : use ( ) - > quarters_at_sample ( _session - > audible_sample ( ) ) ) ;
}
ac - > list ( ) - > set_in_write_pass ( true , true , pos ) ;
2012-07-17 03:10:40 +00:00
}
2012-07-13 21:13:35 +00:00
/* we can't store shared_ptr<Destructible> in connections because it
* creates reference cycles . we don ' t need to make the weak_ptr < >
* explicit here , but it helps to remind us what is going on .
*/
2015-10-05 16:17:49 +02:00
2023-02-16 16:33:28 -07:00
std : : weak_ptr < AutomationControl > wac ( ac ) ;
2017-07-22 15:40:27 +02:00
ac - > DropReferences . connect_same_thread ( automation_connections [ ac ] , boost : : bind ( & AutomationWatch : : remove_weak_automation_watch , this , wac ) ) ;
2012-07-13 21:13:35 +00:00
}
void
2023-02-16 16:33:28 -07:00
AutomationWatch : : remove_weak_automation_watch ( std : : weak_ptr < AutomationControl > wac )
2012-07-13 21:13:35 +00:00
{
2023-02-16 16:33:28 -07:00
std : : shared_ptr < AutomationControl > ac = wac . lock ( ) ;
2012-07-13 21:13:35 +00:00
if ( ! ac ) {
return ;
}
remove_automation_watch ( ac ) ;
}
void
2023-02-16 16:33:28 -07:00
AutomationWatch : : remove_automation_watch ( std : : shared_ptr < AutomationControl > ac )
2012-07-13 21:13:35 +00:00
{
2012-07-25 17:48:55 +00:00
Glib : : Threads : : Mutex : : Lock lm ( automation_watch_lock ) ;
2012-07-13 21:13:35 +00:00
DEBUG_TRACE ( DEBUG : : Automation , string_compose ( " remove control %1 from automation watch \n " , ac - > name ( ) ) ) ;
2012-11-14 15:06:41 +00:00
automation_watches . erase ( ac ) ;
2017-07-22 15:40:27 +02:00
automation_connections . erase ( ac ) ;
2012-07-17 03:10:40 +00:00
ac - > list ( ) - > set_in_write_pass ( false ) ;
2012-07-13 21:13:35 +00:00
}
2017-01-23 13:25:24 +01:00
void
2017-09-18 12:39:17 -04:00
AutomationWatch : : transport_stop_automation_watches ( samplepos_t when )
2017-01-23 13:25:24 +01:00
{
DEBUG_TRACE ( DEBUG : : Automation , " clear all automation watches \n " ) ;
AutomationWatches tmp ;
{
Glib : : Threads : : Mutex : : Lock lm ( automation_watch_lock ) ;
/* copy automation watches */
tmp = automation_watches ;
/* clear existing container so that each
: : remove_automation_watch ( ) call from
AutomationControl : : stop_touch ( ) is faster .
*/
automation_watches . clear ( ) ;
2017-07-22 15:40:27 +02:00
automation_connections . clear ( ) ;
2017-01-23 13:25:24 +01:00
}
for ( AutomationWatches : : iterator i = tmp . begin ( ) ; i ! = tmp . end ( ) ; + + i ) {
2020-09-20 16:34:09 -06:00
( * i ) - > stop_touch ( timepos_t ( when ) ) ;
2017-01-23 13:25:24 +01:00
}
}
2012-07-13 21:13:35 +00:00
gint
AutomationWatch : : timer ( )
{
if ( ! _session | | ! _session - > transport_rolling ( ) ) {
return TRUE ;
}
2022-06-20 08:42:39 -06:00
( void ) Temporal : : TempoMap : : fetch ( ) ;
2012-07-13 21:13:35 +00:00
{
2012-07-25 17:48:55 +00:00
Glib : : Threads : : Mutex : : Lock lm ( automation_watch_lock ) ;
2012-11-14 15:06:41 +00:00
2017-09-18 12:39:17 -04:00
samplepos_t time = _session - > audible_sample ( ) ;
2015-03-25 14:28:25 -05:00
if ( time > _last_time ) { //we only write automation in the forward direction; this fixes automation-recording in a loop
for ( AutomationWatches : : iterator aw = automation_watches . begin ( ) ; aw ! = automation_watches . end ( ) ; + + aw ) {
if ( ( * aw ) - > alist ( ) - > automation_write ( ) ) {
2022-06-29 01:17:03 +02:00
double val = ( * aw ) - > get_double ( ) ;
2023-02-16 16:33:28 -07:00
std : : shared_ptr < SlavableAutomationControl > sc = std : : dynamic_pointer_cast < SlavableAutomationControl > ( * aw ) ;
2017-06-13 18:09:22 +02:00
if ( sc ) {
val = sc - > reduce_by_masters ( val , true ) ;
}
2020-09-20 16:34:09 -06:00
( * aw ) - > list ( ) - > add ( timepos_t ( time ) , val , true ) ;
2015-03-25 14:28:25 -05:00
}
}
2015-06-15 16:35:19 +10:00
} else if ( time ! = _last_time ) { //transport stopped or reversed. stop the automation pass and start a new one (for bonus points, someday store the previous pass in an undo record)
2015-03-25 14:28:25 -05:00
for ( AutomationWatches : : iterator aw = automation_watches . begin ( ) ; aw ! = automation_watches . end ( ) ; + + aw ) {
2015-10-04 14:51:05 -04:00
DEBUG_TRACE ( DEBUG : : Automation , string_compose ( " %1: transport in rewind, speed %2, in write pass ? %3 writing ? %4 \n " ,
2015-03-25 14:47:18 -05:00
( * aw ) - > name ( ) , _session - > transport_speed ( ) , _session - > transport_rolling ( ) ,
2015-03-25 14:28:25 -05:00
( * aw ) - > alist ( ) - > automation_write ( ) ) ) ;
( * aw ) - > list ( ) - > set_in_write_pass ( false ) ;
if ( ( * aw ) - > alist ( ) - > automation_write ( ) ) {
2020-09-20 16:34:09 -06:00
( * aw ) - > list ( ) - > set_in_write_pass ( true , true , timepos_t ( time ) ) ;
2015-03-25 14:28:25 -05:00
}
2012-07-17 03:10:40 +00:00
}
2012-07-13 21:13:35 +00:00
}
2015-10-05 16:17:49 +02:00
2015-03-25 14:28:25 -05:00
_last_time = time ;
2012-07-13 21:13:35 +00:00
}
return TRUE ;
}
void
AutomationWatch : : thread ( )
{
2024-09-28 03:49:59 +02:00
pbd_set_thread_priority ( pthread_self ( ) , PBD_SCHED_FIFO , PBD_RT_PRI_CTRL ) ;
2012-07-13 21:13:35 +00:00
while ( _run_thread ) {
2022-02-28 22:34:11 +01:00
Glib : : usleep ( ( gulong ) floor ( Config - > get_automation_interval_msecs ( ) * 1000 ) ) ; // TODO use pthread_cond_timedwait on _run_thread
2012-07-13 21:13:35 +00:00
timer ( ) ;
}
}
void
AutomationWatch : : set_session ( Session * s )
{
2012-07-17 03:10:40 +00:00
transport_connection . disconnect ( ) ;
2012-07-13 21:13:35 +00:00
if ( _thread ) {
_run_thread = false ;
_thread - > join ( ) ;
_thread = 0 ;
}
SessionHandlePtr : : set_session ( s ) ;
if ( _session ) {
_run_thread = true ;
2024-09-28 00:14:45 +02:00
_thread = PBD : : Thread : : create ( boost : : bind ( & AutomationWatch : : thread , this ) , " AutomationWatch " ) ;
2015-10-05 16:17:49 +02:00
2012-07-17 03:10:40 +00:00
_session - > TransportStateChange . connect_same_thread ( transport_connection , boost : : bind ( & AutomationWatch : : transport_state_change , this ) ) ;
}
}
void
AutomationWatch : : transport_state_change ( )
{
if ( ! _session ) {
return ;
}
2020-06-12 05:08:29 +02:00
bool rolling = _session - > transport_state_rolling ( ) ;
2015-10-05 16:17:49 +02:00
2017-09-18 12:39:17 -04:00
_last_time = _session - > audible_sample ( ) ;
2012-07-17 03:10:40 +00:00
{
2012-07-25 17:48:55 +00:00
Glib : : Threads : : Mutex : : Lock lm ( automation_watch_lock ) ;
2012-07-17 03:10:40 +00:00
for ( AutomationWatches : : iterator aw = automation_watches . begin ( ) ; aw ! = automation_watches . end ( ) ; + + aw ) {
2015-10-04 14:51:05 -04:00
DEBUG_TRACE ( DEBUG : : Automation , string_compose ( " %1: transport state changed, speed %2, in write pass ? %3 writing ? %4 \n " ,
2012-07-17 03:10:40 +00:00
( * aw ) - > name ( ) , _session - > transport_speed ( ) , rolling ,
( * aw ) - > alist ( ) - > automation_write ( ) ) ) ;
if ( rolling & & ( * aw ) - > alist ( ) - > automation_write ( ) ) {
( * aw ) - > list ( ) - > set_in_write_pass ( true ) ;
} else {
( * aw ) - > list ( ) - > set_in_write_pass ( false ) ;
}
}
2012-07-13 21:13:35 +00:00
}
}