2021-07-17 15:21:45 -04:00
# include <iostream>
2021-08-10 22:35:39 -04:00
# include <cstdlib>
2021-08-13 18:08:17 -04:00
2021-08-06 23:26:50 -04:00
# include <glibmm.h>
2021-08-06 15:03:28 -04:00
# include <rubberband/RubberBandStretcher.h>
2021-07-28 00:39:42 -04:00
# include "pbd/basename.h"
2021-08-08 21:08:16 -04:00
# include "pbd/compose.h"
2021-07-19 11:57:31 -04:00
# include "pbd/failed_constructor.h"
2021-08-31 18:46:19 -04:00
# include "pbd/types_convert.h"
2021-07-19 11:57:31 -04:00
2021-08-05 17:03:28 -04:00
# include "temporal/tempo.h"
2021-07-17 15:21:45 -04:00
# include "ardour/audioregion.h"
# include "ardour/audio_buffer.h"
2021-08-08 21:08:16 -04:00
# include "ardour/debug.h"
2021-07-17 15:21:45 -04:00
# include "ardour/midi_buffer.h"
2021-10-20 18:19:09 -04:00
# include "ardour/midi_region.h"
2021-10-06 17:20:52 -04:00
# include "ardour/minibpm.h"
2021-10-08 22:35:25 -04:00
# include "ardour/port.h"
2021-07-17 15:21:45 -04:00
# include "ardour/region_factory.h"
2021-07-20 00:37:17 -04:00
# include "ardour/session.h"
2021-08-05 18:20:37 -04:00
# include "ardour/session_object.h"
2021-10-08 15:37:29 -04:00
# include "ardour/sidechain.h"
2021-09-01 00:35:14 -04:00
# include "ardour/source_factory.h"
2021-07-17 15:21:45 -04:00
# include "ardour/sndfilesource.h"
# include "ardour/triggerbox.h"
2021-09-26 23:59:15 -04:00
# include "ardour/types_convert.h"
2021-07-17 15:21:45 -04:00
2021-09-27 09:49:41 -04:00
# include "pbd/i18n.h"
2021-07-17 15:21:45 -04:00
using namespace PBD ;
using namespace ARDOUR ;
using std : : string ;
using std : : cerr ;
using std : : endl ;
2021-08-07 18:20:36 -04:00
namespace ARDOUR {
namespace Properties {
2021-09-26 23:59:15 -04:00
PBD : : PropertyDescriptor < bool > use_follow ;
2021-08-07 18:20:36 -04:00
PBD : : PropertyDescriptor < bool > running ;
2021-09-05 12:40:58 -04:00
PBD : : PropertyDescriptor < bool > legato ;
2021-09-29 20:00:55 -04:00
PBD : : PropertyDescriptor < bool > quantization ;
2021-10-01 20:39:06 -04:00
PBD : : PropertyDescriptor < Trigger : : LaunchStyle > launch_style ;
PBD : : PropertyDescriptor < Trigger : : FollowAction > follow_action0 ;
PBD : : PropertyDescriptor < Trigger : : FollowAction > follow_action1 ;
2021-08-07 18:20:36 -04:00
}
}
2021-09-10 15:12:14 -04:00
Trigger : : Trigger ( uint64_t n , TriggerBox & b )
2021-08-08 21:08:16 -04:00
: _box ( b )
, _state ( Stopped )
, _requested_state ( None )
, _bang ( 0 )
, _unbang ( 0 )
, _index ( n )
2021-08-10 22:35:39 -04:00
, _launch_style ( Toggle )
2021-09-26 23:59:15 -04:00
, _use_follow ( Properties : : use_follow , true )
2021-08-13 18:03:36 -04:00
, _follow_action { NextTrigger , Stop }
2021-08-13 00:59:04 -04:00
, _follow_action_probability ( 100 )
2021-10-26 19:34:06 -04:00
, _loop_cnt ( 0 )
2021-10-07 00:40:37 -04:00
, _follow_count ( 1 )
2021-08-08 21:08:16 -04:00
, _quantization ( Temporal : : BBT_Offset ( 0 , 1 , 0 ) )
2021-10-14 15:11:08 -04:00
, _legato ( Properties : : legato , false )
2021-10-05 23:09:16 -04:00
, _stretch ( 1.0 )
2021-10-07 10:03:46 -04:00
, _barcnt ( 0. )
2021-09-10 15:04:49 -04:00
, _ui ( 0 )
2021-10-19 14:52:32 -04:00
, _gain ( 1.0 )
, _pending_gain ( 1.0 )
, _midi_velocity_effect ( 0. )
2021-08-07 18:20:36 -04:00
{
2021-09-26 23:59:15 -04:00
add_property ( _legato ) ;
add_property ( _use_follow ) ;
}
void
Trigger : : set_use_follow ( bool yn )
{
_use_follow = yn ;
2021-09-28 19:45:35 -04:00
PropertyChanged ( Properties : : use_follow ) ;
2021-08-07 18:20:36 -04:00
}
2021-08-31 13:53:24 -04:00
void
Trigger : : set_name ( std : : string const & str )
{
_name = str ;
}
2021-09-10 15:04:49 -04:00
void
Trigger : : set_ui ( void * p )
{
_ui = p ;
}
2021-08-08 21:08:16 -04:00
void
Trigger : : bang ( )
2021-07-17 15:21:45 -04:00
{
2021-10-08 15:37:29 -04:00
if ( ! _region ) {
return ;
}
2021-08-08 21:08:16 -04:00
_bang . fetch_add ( 1 ) ;
2021-08-30 18:31:35 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " bang on %1 \n " , _index ) ) ;
2021-07-20 00:37:17 -04:00
}
2021-08-08 21:08:16 -04:00
void
Trigger : : unbang ( )
2021-08-05 14:10:40 -04:00
{
2021-10-08 15:37:29 -04:00
if ( ! _region ) {
return ;
}
2021-08-08 21:08:16 -04:00
_unbang . fetch_add ( 1 ) ;
2021-08-30 18:31:35 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " un-bang on %1 \n " , _index ) ) ;
2021-08-05 14:10:40 -04:00
}
2021-10-19 14:52:32 -04:00
void
Trigger : : set_gain ( gain_t g )
{
_pending_gain = g ;
}
void
Trigger : : set_midi_velocity_effect ( float mve )
{
_midi_velocity_effect = std : : min ( 1.f , std : : max ( 0.f , mve ) ) ;
}
2021-10-07 00:40:37 -04:00
void
Trigger : : set_follow_count ( uint32_t n )
{
_follow_count = n ;
}
2021-08-08 21:08:16 -04:00
void
2021-09-10 15:12:14 -04:00
Trigger : : set_follow_action ( FollowAction f , uint64_t n )
2021-07-17 15:21:45 -04:00
{
2021-08-12 00:54:13 -04:00
assert ( n < 2 ) ;
_follow_action [ n ] = f ;
2021-10-01 20:39:06 -04:00
if ( n = = 0 ) {
PropertyChanged ( Properties : : follow_action0 ) ;
} else {
PropertyChanged ( Properties : : follow_action1 ) ;
}
2021-07-19 11:57:31 -04:00
}
2021-08-06 23:26:50 -04:00
void
2021-08-08 21:08:16 -04:00
Trigger : : set_launch_style ( LaunchStyle l )
2021-08-06 23:26:50 -04:00
{
2021-08-08 21:08:16 -04:00
_launch_style = l ;
2021-08-11 20:25:46 -04:00
set_usable_length ( ) ;
2021-10-01 20:39:06 -04:00
PropertyChanged ( Properties : : launch_style ) ;
2021-08-06 23:26:50 -04:00
}
2021-08-08 21:08:16 -04:00
XMLNode &
Trigger : : get_state ( void )
2021-07-19 11:57:31 -04:00
{
2021-08-08 21:08:16 -04:00
XMLNode * node = new XMLNode ( X_ ( " Trigger " ) ) ;
2021-08-31 18:46:19 -04:00
2021-09-26 23:59:15 -04:00
for ( OwnedPropertyList : : iterator i = _properties - > begin ( ) ; i ! = _properties - > end ( ) ; + + i ) {
i - > second - > get_value ( * node ) ;
}
2021-08-31 18:46:19 -04:00
node - > set_property ( X_ ( " launch-style " ) , enum_2_string ( _launch_style ) ) ;
node - > set_property ( X_ ( " follow-action-0 " ) , enum_2_string ( _follow_action [ 0 ] ) ) ;
node - > set_property ( X_ ( " follow-action-1 " ) , enum_2_string ( _follow_action [ 1 ] ) ) ;
node - > set_property ( X_ ( " quantization " ) , _quantization ) ;
node - > set_property ( X_ ( " name " ) , _name ) ;
node - > set_property ( X_ ( " index " ) , _index ) ;
2021-10-05 23:09:16 -04:00
node - > set_property ( X_ ( " stretch " ) , _stretch ) ;
2021-08-31 18:46:19 -04:00
if ( _region ) {
node - > set_property ( X_ ( " region " ) , _region - > id ( ) ) ;
}
2021-08-08 21:08:16 -04:00
return * node ;
2021-07-19 11:57:31 -04:00
}
2021-08-08 21:08:16 -04:00
int
2021-08-31 20:36:16 -04:00
Trigger : : set_state ( const XMLNode & node , int version )
2021-07-19 11:57:31 -04:00
{
2021-09-26 23:59:15 -04:00
PropertyChange what_changed ;
what_changed = set_values ( node ) ;
2021-08-31 20:36:16 -04:00
node . get_property ( X_ ( " launch-style " ) , _launch_style ) ;
node . get_property ( X_ ( " follow-action-0 " ) , _follow_action [ 0 ] ) ;
node . get_property ( X_ ( " follow-action-1 " ) , _follow_action [ 1 ] ) ;
node . get_property ( X_ ( " quantization " ) , _quantization ) ;
node . get_property ( X_ ( " name " ) , _name ) ;
node . get_property ( X_ ( " index " ) , _index ) ;
2021-09-01 00:35:14 -04:00
PBD : : ID rid ;
node . get_property ( X_ ( " region " ) , rid ) ;
boost : : shared_ptr < Region > r = RegionFactory : : region_by_id ( rid ) ;
if ( r ) {
set_region ( r ) ;
}
2021-08-08 21:08:16 -04:00
return 0 ;
2021-07-17 15:21:45 -04:00
}
2021-08-12 01:09:36 -04:00
2021-09-05 12:40:58 -04:00
void
Trigger : : set_legato ( bool yn )
{
_legato = yn ;
PropertyChanged ( Properties : : legato ) ;
}
2021-08-12 01:09:36 -04:00
void
Trigger : : set_follow_action_probability ( int n )
{
n = std : : min ( 100 , n ) ;
n = std : : max ( 0 , n ) ;
_follow_action_probability = n ;
}
2021-08-08 21:08:16 -04:00
void
Trigger : : set_quantization ( Temporal : : BBT_Offset const & q )
2021-07-17 15:21:45 -04:00
{
2021-08-08 21:08:16 -04:00
_quantization = q ;
2021-08-11 20:25:46 -04:00
set_usable_length ( ) ;
2021-09-29 20:00:55 -04:00
PropertyChanged ( Properties : : quantization ) ;
2021-08-08 21:08:16 -04:00
}
2021-07-17 15:21:45 -04:00
2021-08-08 21:08:16 -04:00
void
Trigger : : set_region_internal ( boost : : shared_ptr < Region > r )
{
_region = r ;
2021-07-17 15:21:45 -04:00
}
2021-08-08 21:08:16 -04:00
Temporal : : BBT_Offset
Trigger : : quantization ( ) const
2021-07-17 15:21:45 -04:00
{
2021-08-08 21:08:16 -04:00
return _quantization ;
2021-07-17 15:21:45 -04:00
}
void
2021-09-04 12:37:36 -04:00
Trigger : : stop ( int next )
2021-07-17 15:21:45 -04:00
{
2021-08-08 21:08:16 -04:00
request_state ( Stopped ) ;
2021-07-17 15:21:45 -04:00
}
2021-08-08 21:08:16 -04:00
void
2021-09-04 12:37:36 -04:00
Trigger : : request_state ( State s )
2021-08-05 17:03:28 -04:00
{
2021-09-04 12:37:36 -04:00
_requested_state . store ( s ) ;
2021-08-05 17:03:28 -04:00
}
2021-08-08 21:08:16 -04:00
void
2021-09-04 12:37:36 -04:00
Trigger : : startup ( )
2021-07-17 15:21:45 -04:00
{
2021-09-04 12:37:36 -04:00
_state = WaitingToStart ;
2021-10-19 14:52:32 -04:00
_gain = _pending_gain ;
2021-10-26 19:34:06 -04:00
_loop_cnt = 0 ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 starts up \n " , name ( ) ) ) ;
2021-09-04 12:37:36 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
}
2021-10-19 14:52:32 -04:00
void
Trigger : : shutdown ( )
{
_state = Stopped ;
_gain = 1.0 ;
2021-10-26 19:34:06 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 shuts down \n " , name ( ) ) ) ;
2021-10-19 14:52:32 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
}
2021-09-05 12:40:58 -04:00
void
Trigger : : jump_start ( )
{
/* this is used when we start a new trigger in legato mode. We do not
wait for quantization .
*/
_state = Running ;
2021-10-14 23:08:19 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 requested state %2 \n " , index ( ) , enum_2_string ( _state ) ) ) ;
2021-09-05 12:40:58 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
}
void
Trigger : : jump_stop ( )
{
/* this is used when we start a new trigger in legato mode. We do not
wait for quantization .
*/
2021-10-19 14:52:32 -04:00
shutdown ( ) ;
2021-10-14 23:08:19 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 requested state %2 \n " , index ( ) , enum_2_string ( _state ) ) ) ;
PropertyChanged ( ARDOUR : : Properties : : running ) ;
}
void
Trigger : : start_stop ( )
{
/* this is used when we start a tell a currently playing trigger to
stop , but wait for quantization first .
*/
_state = WaitingToStop ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 requested state %2 \n " , index ( ) , enum_2_string ( _state ) ) ) ;
2021-09-05 12:40:58 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
}
2021-07-19 11:57:31 -04:00
void
2021-08-08 21:08:16 -04:00
Trigger : : process_state_requests ( )
2021-07-19 11:57:31 -04:00
{
2021-08-08 21:08:16 -04:00
State new_state = _requested_state . exchange ( None ) ;
2021-07-19 11:57:31 -04:00
2021-08-10 22:35:39 -04:00
if ( new_state ! = None & & new_state ! = _state ) {
2021-07-19 11:57:31 -04:00
2021-08-10 22:35:39 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 requested state %2 \n " , index ( ) , enum_2_string ( new_state ) ) ) ;
switch ( new_state ) {
case Stopped :
2021-09-04 12:37:36 -04:00
if ( _state ! = WaitingToStop ) {
2021-09-05 12:40:58 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 %2 => %3 \n " , index ( ) , enum_2_string ( _state ) , enum_2_string ( WaitingToStop ) ) ) ;
2021-09-04 12:37:36 -04:00
_state = WaitingToStop ;
PropertyChanged ( ARDOUR : : Properties : : running ) ;
}
2021-08-10 22:35:39 -04:00
break ;
case Running :
2021-09-05 01:19:47 -04:00
_box . queue_explict ( this ) ;
2021-08-10 22:35:39 -04:00
break ;
default :
break ;
}
2021-07-19 11:57:31 -04:00
}
2021-08-08 21:08:16 -04:00
/* now check bangs/unbangs */
2021-07-27 22:39:40 -04:00
2021-08-08 21:08:16 -04:00
int x ;
2021-07-27 22:39:40 -04:00
2021-08-08 21:08:16 -04:00
while ( ( x = _bang . load ( ) ) ) {
2021-08-05 17:03:28 -04:00
2021-08-08 21:08:16 -04:00
_bang . fetch_sub ( 1 ) ;
2021-08-05 17:03:28 -04:00
2021-08-10 22:35:39 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 handling bang with state = %2 \n " , index ( ) , enum_2_string ( _state ) ) ) ;
2021-08-08 21:08:16 -04:00
switch ( _state ) {
case None :
abort ( ) ;
break ;
2021-08-05 17:03:28 -04:00
2021-08-08 21:08:16 -04:00
case Running :
switch ( launch_style ( ) ) {
2021-08-12 00:16:16 -04:00
case OneShot :
2021-09-05 12:40:58 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 oneshot %2 => %3 \n " , index ( ) , enum_2_string ( Running ) , enum_2_string ( WaitingForRetrigger ) ) ) ;
2021-08-10 22:35:39 -04:00
_state = WaitingForRetrigger ;
2021-08-31 13:53:24 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
2021-08-08 21:08:16 -04:00
break ;
case Gate :
case Toggle :
case Repeat :
2021-09-05 12:40:58 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 %2 gate/toggle/repeat => %3 \n " , index ( ) , enum_2_string ( Running ) , enum_2_string ( WaitingToStop ) ) ) ;
2021-08-10 22:35:39 -04:00
_state = WaitingToStop ;
2021-09-05 12:40:58 -04:00
_box . clear_implicit ( ) ;
2021-08-31 13:53:24 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
2021-08-08 21:08:16 -04:00
}
break ;
2021-08-05 17:03:28 -04:00
2021-08-08 21:08:16 -04:00
case Stopped :
2021-09-05 12:40:58 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 %2 stopped => %3 \n " , index ( ) , enum_2_string ( Stopped ) , enum_2_string ( WaitingToStart ) ) ) ;
2021-09-05 01:19:47 -04:00
_box . queue_explict ( this ) ;
2021-08-08 21:08:16 -04:00
break ;
2021-08-05 17:03:28 -04:00
2021-08-08 21:08:16 -04:00
case WaitingToStart :
case WaitingToStop :
2021-08-10 22:35:39 -04:00
case WaitingForRetrigger :
2021-08-08 21:08:16 -04:00
case Stopping :
break ;
}
}
2021-07-17 15:21:45 -04:00
2021-08-08 21:08:16 -04:00
while ( ( x = _unbang . load ( ) ) ) {
2021-07-19 11:57:31 -04:00
2021-08-08 21:08:16 -04:00
_unbang . fetch_sub ( 1 ) ;
2021-07-19 11:57:31 -04:00
2021-08-12 14:42:29 -04:00
if ( _launch_style = = Gate | | _launch_style = = Repeat ) {
2021-08-11 16:18:34 -04:00
switch ( _state ) {
case Running :
_state = WaitingToStop ;
2021-08-31 13:53:24 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
2021-08-11 16:18:34 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 unbanged, now in WaitingToStop \n " , index ( ) ) ) ;
break ;
default :
/* didn't even get started */
2021-10-19 14:52:32 -04:00
shutdown ( ) ;
2021-08-11 16:18:34 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 unbanged, never started, now stopped \n " , index ( ) ) ) ;
}
2021-08-08 21:08:16 -04:00
}
}
}
2021-07-19 11:57:31 -04:00
2021-08-10 22:35:39 -04:00
Trigger : : RunType
Trigger : : maybe_compute_next_transition ( Temporal : : Beats const & start , Temporal : : Beats const & end )
2021-08-08 21:08:16 -04:00
{
2021-10-14 23:08:34 -04:00
using namespace Temporal ;
2021-08-10 22:35:39 -04:00
/* In these states, we are not waiting for a transition */
switch ( _state ) {
case Stopped :
return RunNone ;
case Running :
return RunAll ;
case Stopping :
return RunAll ;
default :
break ;
}
2021-10-25 14:53:27 -04:00
timepos_t transition_time ( BeatTime ) ;
/* XXX need to use global grid here is quantization == zero */
2021-07-19 11:57:31 -04:00
2021-08-08 21:08:16 -04:00
if ( _quantization . bars = = 0 ) {
2021-10-25 14:53:27 -04:00
transition_time = timepos_t ( start . snap_to ( Temporal : : Beats ( _quantization . beats , _quantization . ticks ) ) ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 quantized with %5 start at %2, sb %3 eb %4 \n " , index ( ) , transition_time . beats ( ) , start , end , _quantization ) ) ;
2021-08-08 21:08:16 -04:00
} else {
2021-10-14 23:08:34 -04:00
TempoMap : : SharedPtr tmap ( TempoMap : : use ( ) ) ;
BBT_Time bbt = tmap - > bbt_at ( timepos_t ( start ) ) ;
bbt = bbt . round_up_to_bar ( ) ;
bbt . bars = ( bbt . bars / _quantization . bars ) * _quantization . bars ;
2021-10-25 14:53:27 -04:00
transition_time = timepos_t ( tmap - > quarters_at ( bbt ) ) ;
2021-08-08 21:08:16 -04:00
}
2021-07-19 11:57:31 -04:00
2021-10-25 14:53:27 -04:00
if ( transition_time . beats ( ) > = start & & transition_time < end ) {
2021-08-10 22:35:39 -04:00
2021-10-25 14:53:27 -04:00
transition_samples = transition_time . samples ( ) ;
transition_beats = transition_time . beats ( ) ;
2021-07-19 11:57:31 -04:00
2021-10-26 19:34:06 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 in range, should start/stop at %2 aka %3 \n " , index ( ) , transition_samples , transition_beats ) ) ;
2021-08-08 21:08:16 -04:00
if ( _state = = WaitingToStop ) {
_state = Stopping ;
2021-08-31 13:53:24 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
2021-08-10 22:35:39 -04:00
return RunEnd ;
} else if ( _state = = WaitingToStart ) {
retrigger ( ) ;
_state = Running ;
2021-09-04 12:37:36 -04:00
_box . prepare_next ( _index ) ;
2021-08-31 13:53:24 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
2021-08-10 22:35:39 -04:00
return RunStart ;
} else if ( _state = = WaitingForRetrigger ) {
2021-08-08 21:08:16 -04:00
retrigger ( ) ;
_state = Running ;
2021-09-04 12:37:36 -04:00
_box . prepare_next ( _index ) ;
2021-08-31 13:53:24 -04:00
PropertyChanged ( ARDOUR : : Properties : : running ) ;
2021-08-10 22:35:39 -04:00
return RunAll ;
}
} else {
2021-08-11 16:18:34 -04:00
if ( _state = = WaitingForRetrigger | | _state = = WaitingToStop ) {
2021-08-10 22:35:39 -04:00
/* retrigger time has not been reached, just continue
to play normally until then .
*/
return RunAll ;
2021-08-08 21:08:16 -04:00
}
}
2021-07-19 11:57:31 -04:00
2021-08-10 22:35:39 -04:00
return RunNone ;
}
2021-08-08 21:08:16 -04:00
/*--------------------*/
2021-07-17 21:01:10 -04:00
2021-09-10 15:12:14 -04:00
AudioTrigger : : AudioTrigger ( uint64_t n , TriggerBox & b )
2021-08-08 21:08:16 -04:00
: Trigger ( n , b )
, data ( 0 )
2021-08-10 22:35:39 -04:00
, read_index ( 0 )
2021-08-08 21:08:16 -04:00
, data_length ( 0 )
2021-08-13 00:59:04 -04:00
, _start_offset ( 0 )
2021-08-31 13:53:24 -04:00
, _legato_offset ( 0 )
2021-08-11 20:14:27 -04:00
, usable_length ( 0 )
2021-08-13 00:59:04 -04:00
, last_sample ( 0 )
2021-08-08 21:08:16 -04:00
{
}
AudioTrigger : : ~ AudioTrigger ( )
{
for ( std : : vector < Sample * > : : iterator d = data . begin ( ) ; d ! = data . end ( ) ; + + d ) {
delete * d ;
2021-07-17 15:21:45 -04:00
}
2021-07-20 00:37:17 -04:00
}
2021-07-17 15:21:45 -04:00
2021-09-05 01:19:47 -04:00
void
AudioTrigger : : startup ( )
{
Trigger : : startup ( ) ;
retrigger ( ) ;
}
2021-09-05 12:40:58 -04:00
void
AudioTrigger : : jump_start ( )
{
Trigger : : jump_start ( ) ;
retrigger ( ) ;
}
void
AudioTrigger : : jump_stop ( )
{
Trigger : : jump_stop ( ) ;
retrigger ( ) ;
}
2021-10-04 00:44:03 -04:00
double
AudioTrigger : : position_as_fraction ( ) const
{
if ( ! active ( ) ) {
return 0.0 ;
}
return read_index / ( double ) usable_length ;
}
2021-08-31 18:46:19 -04:00
XMLNode &
AudioTrigger : : get_state ( void )
{
XMLNode & node ( Trigger : : get_state ( ) ) ;
node . set_property ( X_ ( " start " ) , timepos_t ( _start_offset ) ) ;
node . set_property ( X_ ( " length " ) , timepos_t ( usable_length ) ) ;
return node ;
}
int
2021-08-31 20:36:16 -04:00
AudioTrigger : : set_state ( const XMLNode & node , int version )
2021-08-31 18:46:19 -04:00
{
2021-08-31 20:36:16 -04:00
timepos_t t ;
2021-09-01 00:35:14 -04:00
if ( ! Trigger : : set_state ( node , version ) ) {
return - 1 ;
}
2021-08-31 20:36:16 -04:00
node . get_property ( X_ ( " start " ) , t ) ;
_start_offset = t . samples ( ) ;
node . get_property ( X_ ( " length " ) , t ) ;
usable_length = t . samples ( ) ;
last_sample = _start_offset + usable_length ;
2021-08-31 18:46:19 -04:00
return 0 ;
}
2021-07-20 00:37:17 -04:00
void
2021-08-31 13:53:24 -04:00
AudioTrigger : : set_start ( timepos_t const & s )
2021-08-13 00:59:04 -04:00
{
_start_offset = s . samples ( ) ;
}
void
2021-08-31 13:53:24 -04:00
AudioTrigger : : set_end ( timepos_t const & e )
2021-08-13 00:59:04 -04:00
{
2021-10-06 17:20:52 -04:00
assert ( ! data . empty ( ) ) ;
2021-10-05 18:01:52 -04:00
set_length ( timecnt_t ( e . samples ( ) - _start_offset , timepos_t ( _start_offset ) ) ) ;
2021-08-13 00:59:04 -04:00
}
2021-08-31 13:53:24 -04:00
void
AudioTrigger : : set_legato_offset ( timepos_t const & offset )
{
_legato_offset = offset . samples ( ) ;
}
timepos_t
AudioTrigger : : current_pos ( ) const
{
return timepos_t ( read_index ) ;
}
2021-08-13 00:59:04 -04:00
void
2021-10-05 18:01:52 -04:00
AudioTrigger : : set_length ( timecnt_t const & newlen )
2021-07-20 00:37:17 -04:00
{
2021-08-08 21:08:16 -04:00
using namespace RubberBand ;
using namespace Temporal ;
2021-08-06 23:26:50 -04:00
2021-08-08 21:08:16 -04:00
if ( ! _region ) {
2021-07-20 00:37:17 -04:00
return ;
}
2021-08-08 21:08:16 -04:00
boost : : shared_ptr < AudioRegion > ar ( boost : : dynamic_pointer_cast < AudioRegion > ( _region ) ) ;
2021-10-05 18:01:52 -04:00
if ( newlen = = _region - > length ( ) ) {
2021-08-08 21:08:16 -04:00
/* no stretch required */
2021-07-19 11:57:31 -04:00
return ;
}
2021-08-08 21:08:16 -04:00
/* offline stretch */
2021-08-05 17:03:28 -04:00
2021-08-08 21:08:16 -04:00
/* study */
2021-08-05 17:03:28 -04:00
2021-08-08 21:08:16 -04:00
const uint32_t nchans = ar - > n_channels ( ) ;
2021-08-05 17:03:28 -04:00
2021-08-08 21:08:16 -04:00
RubberBandStretcher : : Options options = RubberBandStretcher : : Option ( RubberBandStretcher : : OptionProcessOffline | RubberBandStretcher : : OptionStretchPrecise ) ;
RubberBandStretcher stretcher ( _box . session ( ) . sample_rate ( ) , nchans , options , 1.0 , 1.0 ) ;
2021-08-05 17:03:28 -04:00
2021-08-08 21:08:16 -04:00
/* Compute stretch ratio */
2021-07-20 00:37:17 -04:00
2021-08-08 21:08:16 -04:00
if ( newlen . time_domain ( ) = = AudioTime ) {
2021-10-05 23:09:16 -04:00
_stretch = ( double ) newlen . samples ( ) / data_length ;
2021-10-06 17:20:52 -04:00
cerr < < " gonna stretch, ratio is " < < _stretch < < " from " < < newlen . samples ( ) < < " vs. " < < data_length < < endl ;
2021-08-08 21:08:16 -04:00
} else {
/* XXX what to use for position ??? */
2021-08-13 00:59:04 -04:00
timecnt_t l ( newlen , timepos_t ( AudioTime ) ) ;
const timecnt_t dur = TempoMap : : use ( ) - > convert_duration ( l , timepos_t ( 0 ) , AudioTime ) ;
2021-10-05 23:09:16 -04:00
_stretch = ( double ) dur . samples ( ) / data_length ;
2021-07-20 00:37:17 -04:00
}
2021-10-05 23:09:16 -04:00
stretcher . setTimeRatio ( _stretch ) ;
2021-10-05 18:01:52 -04:00
2021-10-05 23:09:16 -04:00
const samplecnt_t expected_length = ceil ( data_length * _stretch ) + 16 ; /* extra space for safety */
2021-08-08 21:08:16 -04:00
std : : vector < Sample * > stretched ;
2021-08-06 15:03:28 -04:00
for ( uint32_t n = 0 ; n < nchans ; + + n ) {
2021-08-06 23:26:50 -04:00
stretched . push_back ( new Sample [ expected_length ] ) ;
2021-08-06 15:03:28 -04:00
}
2021-08-06 23:26:50 -04:00
/* RB expects array-of-ptr-to-Sample, so set one up */
2021-08-29 10:40:59 -04:00
std : : vector < Sample * > raw ( nchans ) ;
std : : vector < Sample * > results ( nchans ) ;
2021-08-06 23:26:50 -04:00
2021-08-06 15:03:28 -04:00
/* study, then process */
2021-08-07 17:08:26 -04:00
const samplecnt_t block_size = 16384 ;
2021-08-06 23:26:50 -04:00
samplecnt_t read = 0 ;
stretcher . setDebugLevel ( 0 ) ;
stretcher . setMaxProcessSize ( block_size ) ;
2021-08-06 15:03:28 -04:00
stretcher . setExpectedInputDuration ( data_length ) ;
2021-08-06 23:26:50 -04:00
while ( read < data_length ) {
2021-08-06 15:03:28 -04:00
2021-08-06 23:26:50 -04:00
for ( uint32_t n = 0 ; n < nchans ; + + n ) {
raw [ n ] = data [ n ] + read ;
}
2021-08-06 15:03:28 -04:00
2021-08-06 23:26:50 -04:00
samplecnt_t to_read = std : : min ( block_size , data_length - read ) ;
read + = to_read ;
2021-08-06 15:03:28 -04:00
2021-08-29 10:40:59 -04:00
stretcher . study ( & raw [ 0 ] , to_read , ( read > = data_length ) ) ;
2021-08-06 15:03:28 -04:00
}
2021-08-06 23:26:50 -04:00
read = 0 ;
2021-08-06 15:03:28 -04:00
2021-08-06 23:26:50 -04:00
samplecnt_t processed = 0 ;
samplecnt_t avail ;
while ( read < data_length ) {
for ( uint32_t n = 0 ; n < nchans ; + + n ) {
raw [ n ] = data [ n ] + read ;
}
samplecnt_t to_read = std : : min ( block_size , data_length - read ) ;
read + = to_read ;
2021-08-29 10:40:59 -04:00
stretcher . process ( & raw [ 0 ] , to_read , ( read > = data_length ) ) ;
2021-08-06 23:26:50 -04:00
while ( ( avail = stretcher . available ( ) ) > 0 ) {
for ( uint32_t n = 0 ; n < nchans ; + + n ) {
results [ n ] = stretched [ n ] + processed ;
}
2021-08-29 10:40:59 -04:00
processed + = stretcher . retrieve ( & results [ 0 ] , avail ) ;
2021-08-06 23:26:50 -04:00
}
2021-08-06 15:03:28 -04:00
}
2021-08-08 21:08:16 -04:00
/* collect final chunk of data, possible delayed by thread activity in stretcher */
2021-08-06 23:26:50 -04:00
while ( ( avail = stretcher . available ( ) ) > = 0 ) {
2021-08-06 15:03:28 -04:00
2021-08-06 23:26:50 -04:00
if ( avail = = 0 ) {
Glib : : usleep ( 10000 ) ;
continue ;
}
2021-08-06 15:03:28 -04:00
2021-08-06 23:26:50 -04:00
for ( uint32_t n = 0 ; n < nchans ; + + n ) {
results [ n ] = stretched [ n ] + processed ;
}
2021-08-06 15:03:28 -04:00
2021-08-29 10:40:59 -04:00
processed + = stretcher . retrieve ( & results [ 0 ] , avail ) ;
2021-08-06 23:26:50 -04:00
}
2021-08-06 15:03:28 -04:00
2021-08-06 23:26:50 -04:00
/* allocate new data buffers */
drop_data ( ) ;
data = stretched ;
data_length = processed ;
2021-08-11 20:14:27 -04:00
if ( ! usable_length | | usable_length > data_length ) {
usable_length = data_length ;
2021-08-13 00:59:04 -04:00
last_sample = _start_offset + usable_length ;
2021-08-11 20:14:27 -04:00
}
2021-08-06 15:03:28 -04:00
}
2021-08-11 20:25:46 -04:00
void
AudioTrigger : : set_usable_length ( )
{
if ( ! _region ) {
return ;
}
switch ( _launch_style ) {
case Repeat :
break ;
default :
usable_length = data_length ;
2021-08-13 00:59:04 -04:00
last_sample = _start_offset + usable_length ;
2021-08-11 20:25:46 -04:00
return ;
}
if ( _quantization = = Temporal : : BBT_Offset ( ) ) {
usable_length = data_length ;
2021-08-13 00:59:04 -04:00
last_sample = _start_offset + usable_length ;
2021-08-11 20:25:46 -04:00
return ;
}
/* XXX MUST HANDLE BAR-LEVEL QUANTIZATION */
timecnt_t len ( Temporal : : Beats ( _quantization . beats , _quantization . ticks ) , timepos_t ( Temporal : : Beats ( ) ) ) ;
usable_length = len . samples ( ) ;
2021-08-13 00:59:04 -04:00
last_sample = _start_offset + usable_length ;
2021-08-11 20:25:46 -04:00
}
2021-08-13 00:59:04 -04:00
timepos_t
2021-08-06 15:03:28 -04:00
AudioTrigger : : current_length ( ) const
{
if ( _region ) {
2021-08-13 00:59:04 -04:00
return timepos_t ( data_length ) ;
2021-08-06 15:03:28 -04:00
}
2021-08-13 00:59:04 -04:00
return timepos_t ( Temporal : : BeatTime ) ;
2021-08-06 15:03:28 -04:00
}
2021-08-13 00:59:04 -04:00
timepos_t
2021-08-06 15:03:28 -04:00
AudioTrigger : : natural_length ( ) const
{
if ( _region ) {
2021-08-13 00:59:04 -04:00
return timepos_t : : from_superclock ( _region - > length ( ) . magnitude ( ) ) ;
2021-08-06 15:03:28 -04:00
}
2021-08-13 00:59:04 -04:00
return timepos_t ( Temporal : : BeatTime ) ;
2021-08-06 15:03:28 -04:00
}
2021-07-19 11:57:31 -04:00
int
AudioTrigger : : set_region ( boost : : shared_ptr < Region > r )
{
boost : : shared_ptr < AudioRegion > ar = boost : : dynamic_pointer_cast < AudioRegion > ( r ) ;
if ( ! ar ) {
return - 1 ;
}
set_region_internal ( r ) ;
2021-10-07 10:03:46 -04:00
load_data ( ar ) ;
compute_and_set_length ( ) ;
2021-07-19 11:57:31 -04:00
2021-10-07 10:03:46 -04:00
PropertyChanged ( ARDOUR : : Properties : : name ) ;
2021-10-06 17:20:52 -04:00
2021-10-07 10:03:46 -04:00
return 0 ;
}
void
AudioTrigger : : compute_and_set_length ( )
{
using namespace Temporal ;
2021-10-06 17:20:52 -04:00
2021-10-05 23:09:16 -04:00
/* now potentially stretch it to match our tempo.
*
* We do not handle tempo changes at present , and should probably issue
* a warming about this .
*/
2021-08-06 15:03:28 -04:00
2021-10-05 18:01:52 -04:00
TempoMap : : SharedPtr tm ( TempoMap : : use ( ) ) ;
2021-10-06 17:20:52 -04:00
2021-10-07 10:03:46 -04:00
if ( _barcnt = = 0 ) {
2021-10-06 17:20:52 -04:00
2021-10-07 10:03:46 -04:00
TempoMetric const & metric ( tm - > metric_at ( timepos_t ( AudioTime ) ) ) ;
2021-10-06 17:20:52 -04:00
breakfastquay : : MiniBPM mbpm ( _box . session ( ) . sample_rate ( ) ) ;
mbpm . setBPMRange ( metric . tempo ( ) . quarter_notes_per_minute ( ) * 0.75 , metric . tempo ( ) . quarter_notes_per_minute ( ) * 1.5 ) ;
double bpm = mbpm . estimateTempoOfSamples ( data [ 0 ] , data_length ) ;
2021-10-07 10:03:46 -04:00
2021-10-11 19:56:31 -04:00
if ( bpm = = 0.0 ) {
/* no apparent tempo, just return since we'll use it as-is */
return ;
}
2021-10-10 19:35:15 -04:00
cerr < < name ( ) < < " Estimated bpm " < < bpm < < " from " < < ( double ) data_length / _box . session ( ) . sample_rate ( ) < < " seconds \n " ;
2021-10-06 17:20:52 -04:00
const double seconds = ( double ) data_length / _box . session ( ) . sample_rate ( ) ;
const double quarters = ( seconds / 60. ) * bpm ;
2021-10-07 10:03:46 -04:00
_barcnt = quarters / metric . meter ( ) . divisions_per_bar ( ) ;
2021-10-06 17:20:52 -04:00
}
/* use initial tempo in map (assumed for now to be the only one */
2021-10-07 10:03:46 -04:00
const samplecnt_t one_bar = tm - > bbt_duration_at ( timepos_t ( AudioTime ) , BBT_Offset ( 1 , 0 , 0 ) ) . samples ( ) ;
2021-10-05 18:01:52 -04:00
2021-10-07 10:03:46 -04:00
cerr < < " one bar in samples: " < < one_bar < < endl ;
cerr < < " barcnt = " < < round ( _barcnt ) < < endl ;
2021-07-19 11:57:31 -04:00
2021-10-07 10:03:46 -04:00
set_length ( timecnt_t ( ( samplecnt_t ) round ( _barcnt ) * one_bar ) ) ;
}
2021-08-05 18:20:37 -04:00
2021-10-07 10:03:46 -04:00
void
AudioTrigger : : tempo_map_change ( )
{
if ( ! _region ) {
return ;
}
drop_data ( ) ;
load_data ( boost : : dynamic_pointer_cast < AudioRegion > ( _region ) ) ;
compute_and_set_length ( ) ;
2021-07-19 11:57:31 -04:00
}
void
AudioTrigger : : drop_data ( )
{
for ( uint32_t n = 0 ; n < data . size ( ) ; + + n ) {
delete [ ] data [ n ] ;
}
data . clear ( ) ;
}
int
2021-10-26 19:34:06 -04:00
2021-07-19 11:57:31 -04:00
AudioTrigger : : load_data ( boost : : shared_ptr < AudioRegion > ar )
{
const uint32_t nchans = ar - > n_channels ( ) ;
2021-08-06 15:03:28 -04:00
data_length = ar - > length_samples ( ) ;
2021-07-19 11:57:31 -04:00
2021-08-11 20:14:27 -04:00
/* if usable length was already set, only adjust it if it is too large */
if ( ! usable_length | | usable_length > data_length ) {
usable_length = data_length ;
2021-08-13 00:59:04 -04:00
last_sample = _start_offset + usable_length ;
2021-08-11 20:14:27 -04:00
}
2021-07-19 11:57:31 -04:00
drop_data ( ) ;
try {
for ( uint32_t n = 0 ; n < nchans ; + + n ) {
2021-08-06 15:03:28 -04:00
data . push_back ( new Sample [ data_length ] ) ;
ar - > read ( data [ n ] , 0 , data_length , n ) ;
2021-07-19 11:57:31 -04:00
}
2021-08-31 13:53:24 -04:00
set_name ( ar - > name ( ) ) ;
2021-07-19 11:57:31 -04:00
} catch ( . . . ) {
drop_data ( ) ;
return - 1 ;
}
return 0 ;
}
2021-07-17 15:21:45 -04:00
void
2021-07-21 01:04:11 -04:00
AudioTrigger : : retrigger ( )
2021-07-17 15:21:45 -04:00
{
2021-08-31 13:53:24 -04:00
read_index = _start_offset + _legato_offset ;
2021-09-05 12:40:58 -04:00
_legato_offset = 0 ; /* used one time only */
2021-09-05 01:19:47 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 retriggered to %2 \n " , _index , read_index ) ) ;
2021-07-19 11:57:31 -04:00
}
2021-08-10 22:35:39 -04:00
int
AudioTrigger : : run ( BufferSet & bufs , pframes_t nframes , pframes_t dest_offset , bool first )
2021-07-17 15:21:45 -04:00
{
2021-08-10 22:35:39 -04:00
boost : : shared_ptr < AudioRegion > ar = boost : : dynamic_pointer_cast < AudioRegion > ( _region ) ;
2021-08-11 16:18:34 -04:00
const bool long_enough_to_fade = ( nframes > = 64 ) ;
2021-08-07 20:53:49 -04:00
2021-08-10 22:35:39 -04:00
assert ( ar ) ;
assert ( active ( ) ) ;
2021-07-17 15:21:45 -04:00
2021-08-10 22:35:39 -04:00
while ( nframes ) {
2021-08-08 21:08:16 -04:00
2021-08-13 00:59:04 -04:00
pframes_t this_read = ( pframes_t ) std : : min ( ( samplecnt_t ) nframes , ( last_sample - read_index ) ) ;
2021-07-17 15:21:45 -04:00
2021-10-10 15:53:24 -04:00
/* Fill all buffers (so mono region will fill all channels),
* and discard extra channels . This models what we do in the
* timeline .
*/
for ( uint64_t chn = 0 ; chn < bufs . count ( ) . n_audio ( ) ; + + chn ) {
2021-08-06 23:26:50 -04:00
2021-09-10 15:12:14 -04:00
uint64_t channel = chn % data . size ( ) ;
2021-08-10 22:35:39 -04:00
Sample * src = data [ channel ] + read_index ;
AudioBuffer & buf ( bufs . get_audio ( chn ) ) ;
2021-08-08 21:08:16 -04:00
2021-08-11 16:18:34 -04:00
2021-08-10 22:35:39 -04:00
if ( first ) {
buf . read_from ( src , this_read , dest_offset ) ;
2021-10-19 14:52:32 -04:00
if ( _gain ! = 1.0 ) {
buf . apply_gain ( _gain , this_read ) ;
}
2021-08-10 22:35:39 -04:00
} else {
2021-10-19 14:52:32 -04:00
if ( _gain ! = 1.0 ) {
buf . accumulate_with_gain_from ( src , this_read , _gain , dest_offset ) ;
} else {
buf . accumulate_with_gain_from ( src , this_read , _gain , dest_offset ) ;
}
2021-08-10 22:35:39 -04:00
}
}
2021-08-06 23:26:50 -04:00
2021-08-10 22:35:39 -04:00
read_index + = this_read ;
2021-08-06 23:26:50 -04:00
2021-08-13 00:59:04 -04:00
if ( read_index > = last_sample ) {
2021-08-06 23:26:50 -04:00
2021-08-10 22:35:39 -04:00
/* We reached the end */
2021-08-07 17:08:26 -04:00
2021-10-26 19:34:06 -04:00
_loop_cnt + + ;
2021-10-07 00:40:37 -04:00
2021-10-26 19:34:06 -04:00
if ( ( _loop_cnt = = _follow_count ) | | ( _launch_style = = Repeat ) | | ( _box . peek_next_trigger ( ) = = this ) ) { /* self repeat */
2021-08-10 22:35:39 -04:00
nframes - = this_read ;
dest_offset + = this_read ;
2021-08-12 14:42:29 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached end, but set to loop, so retrigger \n " , index ( ) ) ) ;
2021-08-10 22:35:39 -04:00
retrigger ( ) ;
/* and go around again */
continue ;
} else {
if ( this_read < nframes ) {
2021-08-06 23:26:50 -04:00
2021-09-10 15:12:14 -04:00
for ( uint64_t chn = 0 ; chn < ar - > n_channels ( ) ; + + chn ) {
uint64_t channel = chn % data . size ( ) ;
2021-08-10 22:35:39 -04:00
AudioBuffer & buf ( bufs . get_audio ( channel ) ) ;
2021-09-05 01:19:47 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 short fill, ri %2 vs ls %3, do silent fill \n " , index ( ) , read_index , last_sample ) ) ;
2021-08-10 22:35:39 -04:00
buf . silence ( nframes - this_read , dest_offset + this_read ) ;
}
}
2021-10-19 14:52:32 -04:00
shutdown ( ) ;
2021-08-12 14:42:29 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached end, now stopped \n " , index ( ) ) ) ;
2021-08-10 22:35:39 -04:00
break ;
}
}
nframes - = this_read ;
2021-08-07 17:08:26 -04:00
}
2021-08-11 16:18:34 -04:00
if ( _state = = Stopping & & long_enough_to_fade ) {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 was stopping, now stopped \n " , index ( ) ) ) ;
2021-10-19 14:52:32 -04:00
shutdown ( ) ;
2021-08-11 16:18:34 -04:00
}
2021-08-10 22:35:39 -04:00
return 0 ;
2021-07-17 15:21:45 -04:00
}
2021-08-08 21:08:16 -04:00
2021-10-20 18:19:09 -04:00
/*--------------------*/
2021-10-24 11:27:14 -04:00
/* Design notes:
for the : : run ( ) call , where we are in process ( ) context , we will use an
RTMidiBuffer as the data structure holding our MIDI . The events here form a
simple array of sample - timestamped MIDI events ( though with the extra
complication of having to handle 2 / 3 byte messages * slightly * differently
from larger messages ) .
This allows us to actually use a simple integer array index to record where
we are during playback and know when we ' ve reached the end .
However , attributes of the trigger like start_offset are kept in BBT_Offsets
or Beats as appropriate , because those are the correct temporal
semantics . This means that we need to refer back to the Region for some
things , since it will be the place where we can look at MIDI events with
musical time stamps ( unlike the sample timestamps in the RTMidiBuffer ) .
To keep things simple , this means that we will only render the actual clip
into the RTMidiBuffer - if start_offset / length reduce the clip from the
Region bounds , we will not place the " excess " in the RTMidiBuffer .
However , if we do any UI display of the clip , we will use the Region for
that , partly because it has music time timestamps and partly because we
already have GUI objects that can operate on MIDIRegions .
*/
2021-10-20 18:19:09 -04:00
MIDITrigger : : MIDITrigger ( uint64_t n , TriggerBox & b )
: Trigger ( n , b )
2021-10-24 11:27:14 -04:00
, read_index ( 0 )
, data_length ( 0 )
, usable_length ( 0 )
2021-10-20 18:19:09 -04:00
, _start_offset ( 0 , 0 , 0 )
, _legato_offset ( 0 , 0 , 0 )
{
}
MIDITrigger : : ~ MIDITrigger ( )
{
}
void
MIDITrigger : : startup ( )
{
Trigger : : startup ( ) ;
retrigger ( ) ;
}
void
MIDITrigger : : jump_start ( )
{
Trigger : : jump_start ( ) ;
retrigger ( ) ;
}
void
MIDITrigger : : jump_stop ( )
{
Trigger : : jump_stop ( ) ;
retrigger ( ) ;
}
double
MIDITrigger : : position_as_fraction ( ) const
{
if ( ! active ( ) ) {
return 0.0 ;
}
2021-10-24 11:27:14 -04:00
if ( data . size ( ) = = 0 ) {
return 0.0 ;
}
2021-10-29 18:22:55 -04:00
if ( read_index > = data . size ( ) ) {
return 1.0 ;
}
2021-10-24 11:27:14 -04:00
const samplepos_t l = data [ read_index ] . timestamp ;
2021-10-20 18:19:09 -04:00
2021-10-24 11:27:14 -04:00
return l / ( double ) usable_length ;
2021-10-20 18:19:09 -04:00
}
XMLNode &
MIDITrigger : : get_state ( void )
{
XMLNode & node ( Trigger : : get_state ( ) ) ;
node . set_property ( X_ ( " start " ) , start_offset ( ) ) ;
node . set_property ( X_ ( " length " ) , timepos_t ( usable_length ) ) ;
return node ;
}
int
MIDITrigger : : set_state ( const XMLNode & node , int version )
{
timepos_t t ;
if ( ! Trigger : : set_state ( node , version ) ) {
return - 1 ;
}
node . get_property ( X_ ( " start " ) , t ) ;
Temporal : : Beats b ( t . beats ( ) ) ;
/* XXX need to deal with bar offsets */
_start_offset = Temporal : : BBT_Offset ( 0 , b . get_beats ( ) , b . get_ticks ( ) ) ;
node . get_property ( X_ ( " length " ) , t ) ;
2021-10-24 11:27:14 -04:00
usable_length = t . samples ( ) ;
2021-10-20 18:19:09 -04:00
return 0 ;
}
void
MIDITrigger : : set_start ( timepos_t const & s )
{
/* XXX need to handle bar offsets */
Temporal : : Beats b ( s . beats ( ) ) ;
_start_offset = Temporal : : BBT_Offset ( 0 , b . get_beats ( ) , b . get_ticks ( ) ) ;
}
void
MIDITrigger : : set_end ( timepos_t const & e )
{
/* XXX need to handle bar offsets */
set_length ( timecnt_t ( e . beats ( ) - Temporal : : Beats ( _start_offset . beats , _start_offset . ticks ) , start_offset ( ) ) ) ;
}
void
MIDITrigger : : set_legato_offset ( timepos_t const & offset )
{
/* XXX need to handle bar offsets */
Temporal : : Beats b ( offset . beats ( ) ) ;
_legato_offset = Temporal : : BBT_Offset ( 0 , b . get_beats ( ) , b . get_ticks ( ) ) ;
}
timepos_t
2021-10-24 11:27:14 -04:00
MIDITrigger : : start_offset ( ) const
2021-10-20 18:19:09 -04:00
{
2021-10-24 11:27:14 -04:00
/* XXX single meter assumption */
Temporal : : Meter const & m = Temporal : : TempoMap : : use ( ) - > meter_at ( Temporal : : Beats ( 0 , 0 ) ) ;
return timepos_t ( m . to_quarters ( _start_offset ) ) ;
2021-10-20 18:19:09 -04:00
}
timepos_t
2021-10-24 11:27:14 -04:00
MIDITrigger : : current_pos ( ) const
2021-10-20 18:19:09 -04:00
{
2021-10-24 11:27:14 -04:00
return timepos_t ( data [ read_index ] . timestamp ) ;
2021-10-20 18:19:09 -04:00
}
void
MIDITrigger : : set_length ( timecnt_t const & newlen )
{
2021-10-24 11:27:14 -04:00
2021-10-20 18:19:09 -04:00
}
void
MIDITrigger : : set_usable_length ( )
{
if ( ! _region ) {
return ;
}
switch ( _launch_style ) {
case Repeat :
break ;
default :
usable_length = data_length ;
return ;
}
if ( _quantization = = Temporal : : BBT_Offset ( ) ) {
usable_length = data_length ;
return ;
}
/* XXX MUST HANDLE BAR-LEVEL QUANTIZATION */
timecnt_t len ( Temporal : : Beats ( _quantization . beats , _quantization . ticks ) , timepos_t ( Temporal : : Beats ( ) ) ) ;
2021-10-24 11:27:14 -04:00
usable_length = len . samples ( ) ;
2021-10-20 18:19:09 -04:00
}
timepos_t
MIDITrigger : : current_length ( ) const
{
if ( _region ) {
return timepos_t ( data_length ) ;
}
return timepos_t ( Temporal : : BeatTime ) ;
}
timepos_t
MIDITrigger : : natural_length ( ) const
{
if ( _region ) {
return timepos_t : : from_ticks ( _region - > length ( ) . magnitude ( ) ) ;
}
return timepos_t ( Temporal : : BeatTime ) ;
}
int
MIDITrigger : : set_region ( boost : : shared_ptr < Region > r )
{
boost : : shared_ptr < MidiRegion > mr = boost : : dynamic_pointer_cast < MidiRegion > ( r ) ;
if ( ! mr ) {
return - 1 ;
}
set_region_internal ( r ) ;
load_data ( mr ) ;
set_length ( mr - > length ( ) ) ;
PropertyChanged ( ARDOUR : : Properties : : name ) ;
return 0 ;
}
void
MIDITrigger : : tempo_map_change ( )
{
}
void
MIDITrigger : : drop_data ( )
{
2021-10-24 11:27:14 -04:00
data . clear ( ) ;
2021-10-20 18:19:09 -04:00
}
int
MIDITrigger : : load_data ( boost : : shared_ptr < MidiRegion > mr )
{
2021-10-24 11:27:14 -04:00
drop_data ( ) ;
2021-10-27 10:36:52 -04:00
2021-10-28 00:54:40 -04:00
/* this generates timestamps in session time. We want trigger-relative
* time ( so the beginning of the region / trigger is zero ) .
*/
2021-10-27 10:36:52 -04:00
mr - > render_range ( data , 0 , Sustained , mr - > start ( ) , mr - > length ( ) , 0 ) ;
2021-10-28 00:54:40 -04:00
const sampleoffset_t shift = mr - > position ( ) . samples ( ) ;
data . shift ( - shift ) ;
2021-10-26 19:34:06 -04:00
set_name ( mr - > name ( ) ) ;
2021-10-29 18:22:55 -04:00
/* There may not be a MIDI event at the end of the region, but we use
* the region size to define how long this trigger is . This allows for
* space at the end of the region to be a part of the timing .
*/
data_length = mr - > length ( ) . samples ( ) ;
2021-10-28 00:54:40 -04:00
2021-10-26 19:34:06 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 loaded midi region, span is %2 \n " , name ( ) , data_length ) ) ;
2021-10-28 00:54:40 -04:00
2021-10-20 18:19:09 -04:00
return 0 ;
}
void
MIDITrigger : : retrigger ( )
{
/* XXX need to deal with bar offsets */
2021-10-24 11:27:14 -04:00
// const Temporal::BBT_Offset o = _start_offset + _legato_offset;
read_index = 0 ;
2021-10-20 18:19:09 -04:00
_legato_offset = Temporal : : BBT_Offset ( ) ;
2021-10-26 19:34:06 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 retriggered to %2, ts = %3 \n " , _index , read_index , transition_samples ) ) ;
2021-10-20 18:19:09 -04:00
}
int
2021-10-26 19:34:06 -04:00
MIDITrigger : : run ( BufferSet & bufs , samplepos_t start_sample , samplepos_t end_sample , pframes_t nframes , pframes_t dest_offset , bool first )
2021-10-20 18:19:09 -04:00
{
2021-10-24 11:27:14 -04:00
MidiBuffer & mb ( bufs . get_midi ( 0 ) ) ;
2021-10-25 14:53:49 -04:00
while ( true ) {
2021-10-24 11:27:14 -04:00
2021-10-29 18:22:55 -04:00
if ( read_index < data . size ( ) ) {
2021-10-24 11:27:14 -04:00
2021-10-29 18:22:55 -04:00
RTMidiBuffer : : Item const & item ( data [ read_index ] ) ;
2021-10-26 19:34:06 -04:00
2021-10-29 18:22:55 -04:00
/* timestamps inside the RTMidiBuffer are relative to
the start of the region .
2021-10-26 19:34:06 -04:00
2021-10-29 18:22:55 -04:00
Offset them to give us process / timeline timestamps .
*/
2021-10-25 14:53:49 -04:00
2021-10-29 18:22:55 -04:00
const samplepos_t effective_time = transition_samples + item . timestamp ;
2021-10-25 14:53:49 -04:00
2021-10-29 18:22:55 -04:00
cerr < < start_sample < < " .. " < < end_sample < < " Item " < < read_index < < " @ " < < item . timestamp < < " + " < < transition_samples < < " = " < < effective_time < < endl ;
2021-10-26 19:34:06 -04:00
2021-10-29 18:22:55 -04:00
if ( effective_time > = start_sample & & effective_time < end_sample ) {
2021-10-25 14:53:49 -04:00
2021-10-29 18:22:55 -04:00
uint32_t sz ;
uint8_t const * bytes = data . bytes ( item , sz ) ;
2021-10-25 14:53:49 -04:00
2021-10-29 18:22:55 -04:00
samplepos_t process_relative_timestamp = effective_time - start_sample ;
const Evoral : : Event < MidiBuffer : : TimeType > ev ( Evoral : : MIDI_EVENT , process_relative_timestamp , sz , const_cast < uint8_t * > ( bytes ) , false ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " inserting %1 \n " , ev ) ) ;
mb . insert_event ( ev ) ;
tracker . track ( bytes ) ;
read_index + + ;
} else {
break ;
}
2021-10-26 19:34:06 -04:00
}
2021-10-29 18:22:55 -04:00
const samplepos_t region_end = transition_samples + data_length ;
if ( read_index > = data . size ( ) | | ( _state = = Running & & region_end > = start_sample & & region_end < = end_sample ) ) {
2021-10-25 14:53:49 -04:00
/* We reached the end */
2021-10-29 18:22:55 -04:00
cerr < < " reached end, ri " < < read_index < < " rend " < < transition_samples + data_length < < " vs end @ " < < end_sample < < endl ;
2021-10-25 14:53:49 -04:00
2021-10-26 19:34:06 -04:00
_loop_cnt + + ;
2021-10-25 14:53:49 -04:00
2021-10-26 19:34:06 -04:00
if ( ( _loop_cnt = = _follow_count ) | | ( _launch_style = = Repeat ) | | ( _box . peek_next_trigger ( ) = = this ) ) { /* self repeat */
2021-10-25 14:53:49 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached end, but set to loop, so retrigger \n " , index ( ) ) ) ;
2021-10-24 11:27:14 -04:00
2021-10-28 00:54:40 -04:00
/* we will "restart" at the beginning of the
next iteration of the trigger .
*/
2021-10-29 18:22:55 -04:00
transition_samples = transition_samples + data_length ;
2021-10-25 14:53:49 -04:00
retrigger ( ) ;
/* and go around again */
continue ;
2021-10-24 11:27:14 -04:00
2021-10-25 14:53:49 -04:00
} else {
shutdown ( ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached end, now stopped \n " , index ( ) ) ) ;
break ;
}
2021-10-24 11:27:14 -04:00
2021-10-25 14:53:49 -04:00
}
}
2021-10-24 11:27:14 -04:00
2021-10-25 14:53:49 -04:00
if ( _state = = Stopping ) {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 was stopping, now stopped \n " , index ( ) ) ) ;
2021-10-29 18:22:55 -04:00
tracker . resolve_notes ( mb , nframes ) ;
2021-10-25 14:53:49 -04:00
shutdown ( ) ;
2021-10-24 11:27:14 -04:00
}
2021-10-20 18:19:09 -04:00
return 0 ;
}
2021-08-08 21:08:16 -04:00
/**************/
void
Trigger : : make_property_quarks ( )
{
Properties : : muted . property_id = g_quark_from_static_string ( X_ ( " running " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for running = %1 \n " , Properties : : running . property_id ) ) ;
2021-09-28 19:45:35 -04:00
Properties : : legato . property_id = g_quark_from_static_string ( X_ ( " legato " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for legato = %1 \n " , Properties : : legato . property_id ) ) ;
Properties : : use_follow . property_id = g_quark_from_static_string ( X_ ( " use-follow " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for use-follow = %1 \n " , Properties : : use_follow . property_id ) ) ;
2021-09-29 20:00:55 -04:00
Properties : : quantization . property_id = g_quark_from_static_string ( X_ ( " quantization " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for quantization = %1 \n " , Properties : : quantization . property_id ) ) ;
2021-10-01 20:39:06 -04:00
Properties : : launch_style . property_id = g_quark_from_static_string ( X_ ( " launch-style " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for quantization = %1 \n " , Properties : : launch_style . property_id ) ) ;
Properties : : follow_action0 . property_id = g_quark_from_static_string ( X_ ( " follow-action-0 " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for follow-action-0 = %1 \n " , Properties : : follow_action0 . property_id ) ) ;
Properties : : follow_action1 . property_id = g_quark_from_static_string ( X_ ( " follow-action-1 " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for follow-action-1 = %1 \n " , Properties : : follow_action1 . property_id ) ) ;
2021-08-08 21:08:16 -04:00
}
2021-11-03 20:44:18 -04:00
const int32_t TriggerBox : : default_triggers_per_box = 8 ;
2021-10-05 23:09:16 -04:00
Temporal : : BBT_Offset TriggerBox : : _assumed_trigger_duration ( 4 , 0 , 0 ) ;
2021-10-19 14:52:32 -04:00
//TriggerBox::TriggerMidiMapMode TriggerBox::_midi_map_mode (TriggerBox::AbletonPush);
TriggerBox : : TriggerMidiMapMode TriggerBox : : _midi_map_mode ( TriggerBox : : SequentialNote ) ;
2021-10-08 22:35:25 -04:00
int TriggerBox : : _first_midi_note = 60 ;
2021-11-01 11:14:02 -04:00
std : : atomic < int32_t > TriggerBox : : _pending_scene ( - 1 ) ;
std : : atomic < int32_t > TriggerBox : : _active_scene ( - 1 ) ;
2021-08-31 13:53:24 -04:00
2021-08-08 21:08:16 -04:00
TriggerBox : : TriggerBox ( Session & s , DataType dt )
: Processor ( s , _ ( " TriggerBox " ) , Temporal : : BeatTime )
, _bang_queue ( 1024 )
, _unbang_queue ( 1024 )
, _data_type ( dt )
2021-10-13 11:07:07 -04:00
, _order ( - 1 )
2021-09-04 12:37:36 -04:00
, explicit_queue ( 64 )
2021-10-19 21:08:46 -04:00
, up_next ( 0 )
2021-09-05 01:19:47 -04:00
, currently_playing ( 0 )
, _stop_all ( false )
2021-11-04 11:50:34 -04:00
, requests ( 1024 )
2021-08-08 21:08:16 -04:00
{
/* default number of possible triggers. call ::add_trigger() to increase */
if ( _data_type = = DataType : : AUDIO ) {
2021-09-10 15:12:14 -04:00
for ( uint64_t n = 0 ; n < default_triggers_per_box ; + + n ) {
2021-08-08 21:08:16 -04:00
all_triggers . push_back ( new AudioTrigger ( n , * this ) ) ;
}
2021-10-20 18:53:52 -04:00
} else {
for ( uint64_t n = 0 ; n < default_triggers_per_box ; + + n ) {
all_triggers . push_back ( new MIDITrigger ( n , * this ) ) ;
}
2021-08-08 21:08:16 -04:00
}
2021-10-07 10:03:46 -04:00
Temporal : : TempoMap : : MapChanged . connect_same_thread ( tempo_map_connection , boost : : bind ( & TriggerBox : : tempo_map_change , this ) ) ;
2021-10-09 18:58:58 -04:00
Config - > ParameterChanged . connect_same_thread ( * this , boost : : bind ( & TriggerBox : : parameter_changed , this , _1 ) ) ;
2021-08-08 21:08:16 -04:00
}
2021-11-01 11:14:02 -04:00
void
TriggerBox : : scene_bang ( uint32_t n )
{
2021-11-01 14:11:34 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " scene bang on %1 for %2 \n " , n ) ) ;
2021-11-01 11:14:02 -04:00
_pending_scene = n ;
}
void
TriggerBox : : scene_unbang ( uint32_t n )
{
}
void
TriggerBox : : maybe_find_scene_bang ( )
{
2021-11-03 20:44:18 -04:00
int32_t pending = _pending_scene . exchange ( - 1 ) ;
2021-11-01 11:14:02 -04:00
if ( pending > = 0 ) {
_active_scene = pending ;
}
}
void
TriggerBox : : clear_scene_bang ( )
{
( void ) _active_scene . exchange ( - 1 ) ;
}
2021-09-05 12:40:58 -04:00
void
TriggerBox : : clear_implicit ( )
{
2021-10-19 21:08:46 -04:00
up_next = 0 ;
2021-09-05 12:40:58 -04:00
}
2021-10-13 11:07:07 -04:00
void
TriggerBox : : set_order ( int32_t n )
{
_order = n ;
}
2021-09-04 12:37:36 -04:00
void
TriggerBox : : queue_explict ( Trigger * t )
{
2021-09-05 01:19:47 -04:00
assert ( t ) ;
2021-09-04 12:37:36 -04:00
explicit_queue . write ( & t , 1 ) ;
2021-10-19 21:08:46 -04:00
clear_implicit ( ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " explicit queue %1, EQ = %2 \n " , t - > index ( ) , explicit_queue . read_space ( ) ) ) ;
2021-09-05 01:19:47 -04:00
if ( currently_playing ) {
currently_playing - > unbang ( ) ;
}
2021-09-04 12:37:36 -04:00
}
2021-09-05 01:19:47 -04:00
Trigger *
2021-09-05 12:40:58 -04:00
TriggerBox : : peek_next_trigger ( )
2021-09-05 01:19:47 -04:00
{
2021-09-05 12:40:58 -04:00
/* allows us to check if there's a next trigger queued, without
* actually reading it from either of the queues .
*/
2021-09-05 01:19:47 -04:00
RingBuffer < Trigger * > : : rw_vector rwv ;
explicit_queue . get_read_vector ( & rwv ) ;
if ( rwv . len [ 0 ] > 0 ) {
return * ( rwv . buf [ 0 ] ) ;
}
2021-10-19 21:08:46 -04:00
return up_next ;
2021-09-05 01:19:47 -04:00
}
2021-09-04 12:37:36 -04:00
Trigger *
TriggerBox : : get_next_trigger ( )
{
Trigger * r ;
if ( explicit_queue . read ( & r , 1 ) = = 1 ) {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " next trigger from explicit queue = %1 \n " , r - > index ( ) ) ) ;
return r ;
}
2021-10-19 21:08:46 -04:00
if ( up_next ) {
r = up_next ;
up_next = 0 ;
2021-09-04 12:37:36 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " next trigger from implicit queue = %1 \n " , r - > index ( ) ) ) ;
return r ;
}
return 0 ;
}
2021-10-24 11:25:11 -04:00
int
TriggerBox : : set_from_selection ( uint64_t slot , boost : : shared_ptr < Region > region )
{
2021-10-26 19:34:06 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " load %1 into %2 \n " , region - > name ( ) , slot ) ) ;
2021-10-24 11:25:11 -04:00
if ( slot > = all_triggers . size ( ) ) {
return 0 ;
}
return all_triggers [ slot ] - > set_region ( region ) ;
}
2021-08-08 21:08:16 -04:00
int
2021-09-10 15:12:14 -04:00
TriggerBox : : set_from_path ( uint64_t slot , std : : string const & path )
2021-08-08 21:08:16 -04:00
{
2021-10-11 19:56:56 -04:00
if ( slot > = all_triggers . size ( ) ) {
return 0 ;
}
2021-08-08 21:08:16 -04:00
try {
SoundFileInfo info ;
string errmsg ;
if ( ! SndFileSource : : get_soundfile_info ( path , info , errmsg ) ) {
error < < string_compose ( _ ( " Cannot get info from audio file %1 (%2) " ) , path , errmsg ) < < endmsg ;
return - 1 ;
}
SourceList src_list ;
for ( uint16_t n = 0 ; n < info . channels ; + + n ) {
2021-09-01 00:35:14 -04:00
boost : : shared_ptr < Source > source ( SourceFactory : : createExternal ( DataType : : AUDIO , _session , path , n , Source : : Flag ( 0 ) , true ) ) ;
if ( ! source ) {
error < < string_compose ( _ ( " Cannot create source from %1 " ) , path ) < < endmsg ;
src_list . clear ( ) ;
return - 1 ;
}
2021-08-08 21:08:16 -04:00
src_list . push_back ( source ) ;
}
PropertyList plist ;
plist . add ( Properties : : start , 0 ) ;
plist . add ( Properties : : length , src_list . front ( ) - > length ( ) ) ;
plist . add ( Properties : : name , basename_nosuffix ( path ) ) ;
plist . add ( Properties : : layer , 0 ) ;
plist . add ( Properties : : layering_index , 0 ) ;
2021-09-01 00:35:14 -04:00
boost : : shared_ptr < Region > the_region ( RegionFactory : : create ( src_list , plist , true ) ) ;
2021-08-08 21:08:16 -04:00
all_triggers [ slot ] - > set_region ( the_region ) ;
/* XXX catch region going away */
} catch ( std : : exception & e ) {
cerr < < " loading sample from " < < path < < " failed: " < < e . what ( ) < < endl ;
return - 1 ;
}
return 0 ;
}
TriggerBox : : ~ TriggerBox ( )
{
drop_triggers ( ) ;
}
2021-09-05 01:19:47 -04:00
void
TriggerBox : : request_stop_all ( )
{
_stop_all = true ;
}
2021-08-08 21:08:16 -04:00
void
TriggerBox : : stop_all ( )
{
/* XXX needs to be done with mutex or via thread-safe queue */
2021-09-10 15:12:14 -04:00
for ( uint64_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
2021-09-04 12:37:36 -04:00
all_triggers [ n ] - > stop ( - 1 ) ;
2021-08-08 21:08:16 -04:00
}
2021-09-05 01:19:47 -04:00
2021-10-19 21:08:46 -04:00
clear_implicit ( ) ;
2021-09-05 01:19:47 -04:00
explicit_queue . reset ( ) ;
2021-08-08 21:08:16 -04:00
}
void
TriggerBox : : drop_triggers ( )
{
Glib : : Threads : : RWLock : : WriterLock lm ( trigger_lock ) ;
for ( Triggers : : iterator t = all_triggers . begin ( ) ; t ! = all_triggers . end ( ) ; + + t ) {
if ( * t ) {
delete * t ;
( * t ) = 0 ;
}
}
2021-08-31 21:03:32 -04:00
all_triggers . clear ( ) ;
2021-08-08 21:08:16 -04:00
}
Trigger *
TriggerBox : : trigger ( Triggers : : size_type n )
{
Glib : : Threads : : RWLock : : ReaderLock lm ( trigger_lock ) ;
if ( n > = all_triggers . size ( ) ) {
return 0 ;
}
return all_triggers [ n ] ;
}
2021-10-08 15:37:29 -04:00
void
2021-10-10 19:35:15 -04:00
TriggerBox : : add_midi_sidechain ( std : : string const & name )
2021-10-08 15:37:29 -04:00
{
if ( ! _sidechain ) {
2021-10-10 19:35:15 -04:00
_sidechain . reset ( new SideChain ( _session , name + " -trig " ) ) ;
2021-10-08 15:37:29 -04:00
_sidechain - > activate ( ) ;
_sidechain - > input ( ) - > add_port ( " " , owner ( ) , DataType : : MIDI ) ; // add a port, don't connect.
2021-10-10 19:35:15 -04:00
boost : : shared_ptr < Port > p = _sidechain - > input ( ) - > nth ( 0 ) ;
if ( p ) {
p - > connect ( Config - > get_default_trigger_input_port ( ) ) ;
} else {
error < < _ ( " Could not create port for trigger side-chain " ) < < endmsg ;
}
2021-10-08 15:37:29 -04:00
}
}
2021-08-08 21:08:16 -04:00
bool
TriggerBox : : can_support_io_configuration ( const ChanCount & in , ChanCount & out )
{
2021-10-08 15:37:29 -04:00
out . set_audio ( std : : max ( out . n_audio ( ) , 2U ) ) ; /* for now, enforce stereo output */
if ( _sidechain ) {
out . set_midi ( std : : max ( out . n_midi ( ) , 1U ) ) ;
2021-08-08 21:08:16 -04:00
}
return true ;
}
bool
TriggerBox : : configure_io ( ChanCount in , ChanCount out )
{
2021-10-08 15:37:29 -04:00
if ( _sidechain ) {
_sidechain - > configure_io ( in , out ) ;
}
2021-08-08 21:08:16 -04:00
return Processor : : configure_io ( in , out ) ;
}
void
TriggerBox : : add_trigger ( Trigger * trigger )
{
Glib : : Threads : : RWLock : : WriterLock lm ( trigger_lock ) ;
all_triggers . push_back ( trigger ) ;
}
2021-10-08 22:35:25 -04:00
void
TriggerBox : : set_midi_map_mode ( TriggerMidiMapMode m )
{
_midi_map_mode = m ;
}
void
TriggerBox : : set_first_midi_note ( int n )
{
_first_midi_note = n ;
}
int
TriggerBox : : note_to_trigger ( int midi_note , int channel )
{
2021-10-13 11:07:07 -04:00
const int column = _order ;
2021-10-08 22:35:25 -04:00
int first_note ;
2021-10-13 11:07:07 -04:00
int top ;
2021-10-08 22:35:25 -04:00
switch ( _midi_map_mode ) {
case AbletonPush :
2021-10-13 11:07:07 -04:00
/* the top row of pads generate MIDI note 92, 93, 94 and so on.
Each lower row generates notes 8 below the one above it .
*/
top = 92 + column ;
for ( int row = 0 ; row < 8 ; + + row ) {
if ( midi_note = = top - ( row * 8 ) ) {
return row ;
}
}
return - 1 ;
break ;
2021-10-08 22:35:25 -04:00
case SequentialNote :
2021-10-19 14:52:32 -04:00
first_note = _first_midi_note + ( column * all_triggers . size ( ) ) ;
2021-10-08 22:35:25 -04:00
return midi_note - first_note ; /* direct access to row */
case ByMidiChannel :
first_note = 3 ;
break ;
default :
break ;
}
return midi_note ;
}
2021-08-08 21:08:16 -04:00
void
TriggerBox : : process_midi_trigger_requests ( BufferSet & bufs )
{
/* check MIDI port input buffers for triggers */
for ( BufferSet : : midi_iterator mi = bufs . midi_begin ( ) ; mi ! = bufs . midi_end ( ) ; + + mi ) {
MidiBuffer & mb ( * mi ) ;
for ( MidiBuffer : : iterator ev = mb . begin ( ) ; ev ! = mb . end ( ) ; + + ev ) {
if ( ! ( * ev ) . is_note ( ) ) {
continue ;
}
2021-10-08 22:35:25 -04:00
int trigger_number = note_to_trigger ( ( * ev ) . note ( ) , ( * ev ) . channel ( ) ) ;
2021-08-08 21:08:16 -04:00
2021-10-13 11:07:07 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " note %1 received on %2, translated to trigger num %3 \n " , ( int ) ( * ev ) . note ( ) , ( int ) ( * ev ) . channel ( ) , trigger_number ) ) ;
2021-10-08 22:35:25 -04:00
if ( trigger_number < 0 ) {
/* not for us */
continue ;
}
2021-08-08 21:08:16 -04:00
2021-10-08 22:35:25 -04:00
if ( trigger_number > ( int ) all_triggers . size ( ) ) {
continue ;
}
2021-08-08 21:08:16 -04:00
2021-10-08 22:35:25 -04:00
Trigger * t = all_triggers [ trigger_number ] ;
2021-08-08 21:08:16 -04:00
2021-10-08 22:35:25 -04:00
if ( ! t ) {
continue ;
}
2021-08-08 21:08:16 -04:00
2021-10-08 22:35:25 -04:00
if ( ( * ev ) . is_note_on ( ) ) {
2021-08-08 21:08:16 -04:00
2021-10-19 14:52:32 -04:00
if ( t - > midi_velocity_effect ( ) ! = 0.0 ) {
/* if MVE is zero, MIDI velocity has no
impact on gain . If it is small , it
has a small effect on gain . As it
approaches 1.0 , it has full control
over the trigger gain .
*/
t - > set_gain ( 1.0 - ( t - > midi_velocity_effect ( ) * ( * ev ) . velocity ( ) / 127.f ) ) ;
}
2021-10-08 22:35:25 -04:00
t - > bang ( ) ;
2021-08-08 21:08:16 -04:00
2021-10-08 22:35:25 -04:00
} else if ( ( * ev ) . is_note_off ( ) ) {
2021-10-08 15:37:29 -04:00
2021-10-08 22:35:25 -04:00
t - > unbang ( ) ;
2021-08-08 21:08:16 -04:00
}
}
}
}
void
TriggerBox : : run ( BufferSet & bufs , samplepos_t start_sample , samplepos_t end_sample , double speed , pframes_t nframes , bool result_required )
{
2021-10-11 19:57:10 -04:00
/* XXX a test to check if we have no usable slots would be good
here . if so , we can just return .
*/
if ( _active ) {
if ( ! _pending_active ) {
_active = false ;
return ;
}
} else {
if ( _pending_active ) {
_active = true ;
} else {
return ;
}
2021-10-11 10:50:58 -04:00
}
2021-08-08 21:08:16 -04:00
if ( start_sample < 0 ) {
/* we can't do anything under these conditions (related to
latency compensation
*/
return ;
}
2021-10-08 15:37:29 -04:00
if ( _sidechain ) {
_sidechain - > run ( bufs , start_sample , end_sample , speed , nframes , true ) ;
}
2021-08-08 21:08:16 -04:00
2021-10-08 15:37:29 -04:00
process_midi_trigger_requests ( bufs ) ;
2021-09-04 12:37:36 -04:00
2021-11-01 14:11:34 -04:00
if ( _active_scene > = 0 ) {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " tb noticed active scene %1 \n " , _active_scene ) ) ;
2021-11-03 20:44:18 -04:00
if ( _active_scene < ( int32_t ) all_triggers . size ( ) ) {
2021-11-01 14:11:34 -04:00
all_triggers [ _active_scene ] - > bang ( ) ;
}
}
2021-09-04 12:37:36 -04:00
/* now let each trigger handle any state changes */
2021-09-10 15:12:14 -04:00
std : : vector < uint64_t > to_run ;
2021-08-08 21:08:16 -04:00
2021-09-10 15:12:14 -04:00
for ( uint64_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
2021-08-08 21:08:16 -04:00
all_triggers [ n ] - > process_state_requests ( ) ;
2021-09-05 01:19:47 -04:00
}
2021-08-08 21:08:16 -04:00
2021-09-05 12:40:58 -04:00
Trigger * nxt = 0 ;
2021-09-05 01:19:47 -04:00
if ( ! currently_playing ) {
if ( ( currently_playing = get_next_trigger ( ) ) ! = 0 ) {
currently_playing - > startup ( ) ;
2021-08-08 21:08:16 -04:00
}
}
2021-09-05 01:19:47 -04:00
if ( ! currently_playing ) {
2021-08-08 21:08:16 -04:00
return ;
}
/* transport must be active for triggers */
if ( ! _session . transport_state_rolling ( ) ) {
_session . start_transport_from_processor ( ) ;
}
timepos_t start ( start_sample ) ;
timepos_t end ( end_sample ) ;
Temporal : : Beats start_beats ( start . beats ( ) ) ;
Temporal : : Beats end_beats ( end . beats ( ) ) ;
Temporal : : TempoMap : : SharedPtr tmap ( Temporal : : TempoMap : : use ( ) ) ;
2021-09-10 15:12:14 -04:00
uint64_t max_chans = 0 ;
2021-08-08 21:08:16 -04:00
bool first = false ;
2021-09-05 12:40:58 -04:00
/* see if there's another trigger explicitly queued that has legato set. */
RingBuffer < Trigger * > : : rw_vector rwv ;
explicit_queue . get_read_vector ( & rwv ) ;
if ( rwv . len [ 0 ] > 0 ) {
2021-10-19 21:08:46 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " explicit queue rvec %1 + 2% \n " , rwv . len [ 0 ] , rwv . len [ 1 ] ) ) ;
2021-09-05 12:40:58 -04:00
/* actually fetch it (guaranteed to pull from the explicit queue */
nxt = get_next_trigger ( ) ;
/* if user triggered same clip, with legato set, then there is
* nothing to do
*/
if ( nxt ! = currently_playing ) {
if ( nxt - > legato ( ) ) {
/* We want to start this trigger immediately, without
* waiting for quantization points , and it should start
* playing at the same internal offset as the current
* trigger .
*/
nxt - > set_legato_offset ( currently_playing - > current_pos ( ) ) ;
nxt - > jump_start ( ) ;
currently_playing - > jump_stop ( ) ;
prepare_next ( nxt - > index ( ) ) ;
/* and switch */
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 => %2 switched to in legato mode \n " , currently_playing - > index ( ) , nxt - > index ( ) ) ) ;
currently_playing = nxt ;
2021-10-14 23:08:19 -04:00
} else {
2021-10-19 21:08:46 -04:00
2021-10-14 23:08:19 -04:00
currently_playing - > start_stop ( ) ;
2021-10-19 21:08:46 -04:00
up_next = all_triggers [ nxt - > index ( ) ] ;
2021-10-14 23:08:19 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " start stop for %1 before switching to %2 \n " , currently_playing - > index ( ) , nxt - > index ( ) ) ) ;
2021-09-05 12:40:58 -04:00
}
}
}
if ( _stop_all ) {
stop_all ( ) ;
_stop_all = false ;
}
2021-09-05 01:19:47 -04:00
while ( currently_playing ) {
2021-08-08 21:08:16 -04:00
2021-09-05 01:19:47 -04:00
assert ( currently_playing - > state ( ) > = Trigger : : WaitingToStart ) ;
2021-08-08 21:08:16 -04:00
2021-08-10 22:35:39 -04:00
Trigger : : RunType rt ;
2021-08-08 21:08:16 -04:00
2021-09-05 01:19:47 -04:00
switch ( currently_playing - > state ( ) ) {
2021-08-08 21:08:16 -04:00
case Trigger : : WaitingToStop :
case Trigger : : WaitingToStart :
2021-08-10 22:35:39 -04:00
case Trigger : : WaitingForRetrigger :
2021-10-26 19:34:06 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 in state %2, recompute next transition \n " , currently_playing - > name ( ) , currently_playing - > state ( ) ) ) ;
2021-09-05 01:19:47 -04:00
rt = currently_playing - > maybe_compute_next_transition ( start_beats , end_beats ) ;
2021-08-08 21:08:16 -04:00
break ;
2021-08-10 22:35:39 -04:00
default :
rt = Trigger : : RunAll ;
2021-08-08 21:08:16 -04:00
}
2021-09-05 01:19:47 -04:00
if ( rt = = Trigger : : RunNone ) {
/* nothing to do at this time, still waiting to start */
return ;
}
boost : : shared_ptr < Region > r = currently_playing - > region ( ) ;
sampleoffset_t dest_offset ;
pframes_t trigger_samples ;
const bool was_waiting_to_start = ( currently_playing - > state ( ) = = Trigger : : WaitingToStart ) ;
2021-08-10 22:35:39 -04:00
2021-09-05 01:19:47 -04:00
if ( rt = = Trigger : : RunEnd ) {
2021-08-08 21:08:16 -04:00
/* trigger will reach it's end somewhere within this
* process cycle , so compute the number of samples it
* should generate .
*/
2021-10-25 14:53:27 -04:00
trigger_samples = nframes - ( currently_playing - > transition_samples - start_sample ) ;
2021-08-08 21:08:16 -04:00
dest_offset = 0 ;
2021-08-10 22:35:39 -04:00
} else if ( rt = = Trigger : : RunStart ) {
2021-08-08 21:08:16 -04:00
/* trigger will start somewhere within this process
* cycle . Compute the sample offset where any audio
* should end up , and the number of samples it should generate .
*/
2021-10-25 14:53:27 -04:00
dest_offset = std : : max ( samplepos_t ( 0 ) , currently_playing - > transition_samples - start_sample ) ;
2021-08-08 21:08:16 -04:00
trigger_samples = nframes - dest_offset ;
2021-08-10 22:35:39 -04:00
} else if ( rt = = Trigger : : RunAll ) {
2021-08-08 21:08:16 -04:00
/* trigger is just running normally, and will fill
* buffers entirely .
*/
dest_offset = 0 ;
trigger_samples = nframes ;
2021-08-10 22:35:39 -04:00
2021-09-05 01:19:47 -04:00
} else {
/* NOTREACHED */
2021-08-10 22:35:39 -04:00
}
if ( was_waiting_to_start ) {
2021-09-05 01:19:47 -04:00
determine_next_trigger ( currently_playing - > index ( ) ) ;
2021-08-08 21:08:16 -04:00
}
2021-09-05 01:19:47 -04:00
AudioTrigger * at = dynamic_cast < AudioTrigger * > ( currently_playing ) ;
2021-08-08 21:08:16 -04:00
if ( at ) {
boost : : shared_ptr < AudioRegion > ar = boost : : dynamic_pointer_cast < AudioRegion > ( r ) ;
2021-09-10 15:12:14 -04:00
const uint64_t nchans = ar - > n_channels ( ) ;
2021-08-08 21:08:16 -04:00
max_chans = std : : max ( max_chans , nchans ) ;
2021-08-10 22:35:39 -04:00
at - > run ( bufs , trigger_samples , dest_offset , first ) ;
first = false ;
} else {
2021-10-26 19:34:06 -04:00
MIDITrigger * mt = dynamic_cast < MIDITrigger * > ( currently_playing ) ;
2021-08-08 21:08:16 -04:00
2021-10-26 19:34:06 -04:00
if ( mt ) {
mt - > run ( bufs , start_sample , end_sample , trigger_samples , dest_offset , first ) ;
first = false ;
}
2021-08-08 21:08:16 -04:00
2021-08-10 22:35:39 -04:00
}
2021-08-08 21:08:16 -04:00
2021-09-05 01:19:47 -04:00
if ( currently_playing - > state ( ) = = Trigger : : Stopped ) {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 did stop \n " , currently_playing - > index ( ) ) ) ;
2021-08-08 21:08:16 -04:00
2021-09-04 12:37:36 -04:00
Trigger * nxt = get_next_trigger ( ) ;
2021-08-08 21:08:16 -04:00
2021-09-05 01:19:47 -04:00
if ( nxt ) {
2021-08-08 21:08:16 -04:00
2021-09-05 01:19:47 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 switching to %2 \n " , currently_playing - > index ( ) , nxt - > index ( ) ) ) ;
2021-09-04 12:37:36 -04:00
if ( nxt - > legato ( ) ) {
2021-09-05 01:19:47 -04:00
nxt - > set_legato_offset ( currently_playing - > current_pos ( ) ) ;
2021-08-08 21:08:16 -04:00
}
2021-09-04 12:37:36 -04:00
/* start it up */
nxt - > startup ( ) ;
2021-09-05 01:19:47 -04:00
currently_playing = nxt ;
} else {
currently_playing = 0 ;
2021-08-08 21:08:16 -04:00
}
2021-09-05 01:19:47 -04:00
} else {
/* done */
break ;
2021-08-10 22:35:39 -04:00
}
}
2021-08-08 21:08:16 -04:00
2021-08-10 22:35:39 -04:00
ChanCount cc ( DataType : : AUDIO , max_chans ) ;
cc . set_midi ( bufs . count ( ) . n_midi ( ) ) ;
bufs . set_count ( cc ) ;
}
2021-08-08 21:08:16 -04:00
2021-08-10 22:35:39 -04:00
void
2021-09-10 15:12:14 -04:00
TriggerBox : : prepare_next ( uint64_t current )
2021-09-04 12:37:36 -04:00
{
int nxt = determine_next_trigger ( current ) ;
2021-09-05 12:40:58 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " nxt for %1 = %2 \n " , current , nxt ) ) ;
2021-09-04 12:37:36 -04:00
if ( nxt > = 0 ) {
2021-10-19 21:08:46 -04:00
up_next = all_triggers [ nxt ] ;
2021-09-04 12:37:36 -04:00
}
}
int
2021-09-10 15:12:14 -04:00
TriggerBox : : determine_next_trigger ( uint64_t current )
2021-08-10 22:35:39 -04:00
{
2021-09-10 15:12:14 -04:00
uint64_t n ;
uint64_t runnable = 0 ;
2021-08-10 22:35:39 -04:00
2021-09-04 12:37:36 -04:00
/* count number of triggers that can actually be run (i.e. they have a region) */
2021-09-10 15:12:14 -04:00
for ( uint64_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
2021-08-10 22:35:39 -04:00
if ( all_triggers [ n ] - > region ( ) ) {
runnable + + ;
}
}
2021-09-04 12:37:36 -04:00
/* decide which of the two follow actions we're going to use (based on
* random number and the probability setting )
*/
2021-08-12 01:03:53 -04:00
int which_follow_action ;
2021-08-15 10:04:08 -04:00
int r = _pcg . rand ( 100 ) ; // 0 .. 99
2021-08-12 01:03:53 -04:00
if ( r < = all_triggers [ current ] - > follow_action_probability ( ) ) {
which_follow_action = 0 ;
} else {
which_follow_action = 1 ;
}
2021-09-04 12:37:36 -04:00
/* first switch: deal with the "special" cases where we either do
2021-09-05 01:19:47 -04:00
* nothing or just repeat the current trigger
2021-09-04 12:37:36 -04:00
*/
2021-08-12 01:03:53 -04:00
switch ( all_triggers [ current ] - > follow_action ( which_follow_action ) ) {
2021-08-10 22:35:39 -04:00
case Trigger : : Stop :
2021-09-04 12:37:36 -04:00
return - 1 ;
2021-08-12 01:03:53 -04:00
2021-08-10 22:35:39 -04:00
case Trigger : : QueuedTrigger :
/* XXX implement me */
2021-09-04 12:37:36 -04:00
return - 1 ;
2021-08-10 22:35:39 -04:00
default :
if ( runnable = = 1 ) {
2021-09-04 12:37:36 -04:00
/* there's only 1 runnable trigger, so the "next" one
is the same as the current one .
*/
return current ;
2021-08-10 22:35:39 -04:00
}
}
2021-09-04 12:37:36 -04:00
/* second switch: handle the "real" follow actions */
2021-08-12 01:03:53 -04:00
switch ( all_triggers [ current ] - > follow_action ( which_follow_action ) ) {
2021-08-12 00:54:13 -04:00
case Trigger : : Again :
2021-09-04 12:37:36 -04:00
return current ;
2021-08-12 00:54:13 -04:00
2021-08-10 22:35:39 -04:00
case Trigger : : NextTrigger :
n = current ;
while ( true ) {
+ + n ;
if ( n > = all_triggers . size ( ) ) {
n = 0 ;
2021-08-08 21:08:16 -04:00
}
2021-08-10 22:35:39 -04:00
if ( n = = current ) {
2021-09-05 12:40:58 -04:00
cerr < < " outa here \n " ;
2021-08-10 22:35:39 -04:00
break ;
}
2021-08-08 21:08:16 -04:00
2021-08-10 22:35:39 -04:00
if ( all_triggers [ n ] - > region ( ) & & ! all_triggers [ n ] - > active ( ) ) {
2021-09-04 12:37:36 -04:00
return n ;
2021-08-10 22:35:39 -04:00
}
}
break ;
case Trigger : : PrevTrigger :
n = current ;
while ( true ) {
if ( n = = 0 ) {
n = all_triggers . size ( ) - 1 ;
} else {
n - = 1 ;
}
2021-08-08 21:08:16 -04:00
2021-08-10 22:35:39 -04:00
if ( n = = current ) {
break ;
}
if ( all_triggers [ n ] - > region ( ) & & ! all_triggers [ n ] - > active ( ) ) {
2021-09-04 12:37:36 -04:00
return n ;
2021-08-10 22:35:39 -04:00
}
2021-08-08 21:08:16 -04:00
}
2021-08-10 22:35:39 -04:00
break ;
2021-08-08 21:08:16 -04:00
2021-08-10 22:35:39 -04:00
case Trigger : : FirstTrigger :
for ( n = 0 ; n < all_triggers . size ( ) ; + + n ) {
if ( all_triggers [ n ] - > region ( ) & & ! all_triggers [ n ] - > active ( ) ) {
2021-09-04 12:37:36 -04:00
return n ;
2021-08-10 22:35:39 -04:00
}
2021-08-08 21:08:16 -04:00
}
2021-08-10 22:35:39 -04:00
break ;
case Trigger : : LastTrigger :
2021-08-11 20:14:37 -04:00
for ( int i = all_triggers . size ( ) - 1 ; i > = 0 ; - - i ) {
if ( all_triggers [ i ] - > region ( ) & & ! all_triggers [ i ] - > active ( ) ) {
2021-09-04 12:37:36 -04:00
return i ;
2021-08-10 22:35:39 -04:00
}
}
break ;
case Trigger : : AnyTrigger :
while ( true ) {
2021-08-15 10:04:08 -04:00
n = _pcg . rand ( all_triggers . size ( ) ) ;
2021-08-10 22:35:39 -04:00
if ( ! all_triggers [ n ] - > region ( ) ) {
continue ;
}
if ( all_triggers [ n ] - > active ( ) ) {
continue ;
}
break ;
}
2021-09-04 12:37:36 -04:00
return n ;
2021-08-10 22:35:39 -04:00
case Trigger : : OtherTrigger :
while ( true ) {
2021-08-15 10:04:08 -04:00
n = _pcg . rand ( all_triggers . size ( ) ) ;
2021-09-10 15:12:14 -04:00
if ( ( uint64_t ) n = = current ) {
2021-08-10 22:35:39 -04:00
continue ;
}
if ( ! all_triggers [ n ] - > region ( ) ) {
continue ;
}
if ( all_triggers [ n ] - > active ( ) ) {
continue ;
}
break ;
}
2021-09-04 12:37:36 -04:00
return n ;
2021-08-10 22:35:39 -04:00
/* NOTREACHED */
case Trigger : : Stop :
case Trigger : : QueuedTrigger :
break ;
2021-08-08 21:08:16 -04:00
}
2021-09-04 12:37:36 -04:00
return current ;
2021-08-08 21:08:16 -04:00
}
XMLNode &
TriggerBox : : get_state ( void )
{
2021-08-31 18:46:19 -04:00
XMLNode & node ( Processor : : get_state ( ) ) ;
2021-08-31 21:03:32 -04:00
node . set_property ( X_ ( " type " ) , X_ ( " triggerbox " ) ) ;
2021-08-31 18:46:19 -04:00
node . set_property ( X_ ( " data-type " ) , _data_type . to_string ( ) ) ;
2021-10-13 11:07:07 -04:00
node . set_property ( X_ ( " order " ) , _order ) ;
2021-08-31 18:46:19 -04:00
XMLNode * trigger_child ( new XMLNode ( X_ ( " Triggers " ) ) ) ;
{
Glib : : Threads : : RWLock : : ReaderLock lm ( trigger_lock ) ;
for ( Triggers : : iterator t = all_triggers . begin ( ) ; t ! = all_triggers . end ( ) ; + + t ) {
trigger_child - > add_child_nocopy ( ( * t ) - > get_state ( ) ) ;
}
}
node . add_child_nocopy ( * trigger_child ) ;
2021-10-08 15:37:29 -04:00
if ( _sidechain ) {
XMLNode * scnode = new XMLNode ( X_ ( " Sidechain " ) ) ;
2021-10-10 19:35:15 -04:00
std : : string port_name = _sidechain - > input ( ) - > nth ( 0 ) - > name ( ) ;
port_name = port_name . substr ( 0 , port_name . find ( ' - ' ) ) ;
scnode - > set_property ( X_ ( " name " ) , port_name ) ;
2021-10-08 15:37:29 -04:00
scnode - > add_child_nocopy ( _sidechain - > get_state ( ) ) ;
node . add_child_nocopy ( * scnode ) ;
}
2021-08-31 18:46:19 -04:00
return node ;
2021-08-08 21:08:16 -04:00
}
int
2021-08-31 21:03:32 -04:00
TriggerBox : : set_state ( const XMLNode & node , int version )
2021-08-08 21:08:16 -04:00
{
2021-10-19 14:07:52 -04:00
Processor : : set_state ( node , version ) ;
2021-08-31 21:03:32 -04:00
node . get_property ( X_ ( " data-type " ) , _data_type ) ;
2021-10-13 11:07:07 -04:00
node . get_property ( X_ ( " order " ) , _order ) ;
2021-08-31 21:03:32 -04:00
XMLNode * tnode ( node . child ( X_ ( " Triggers " ) ) ) ;
assert ( tnode ) ;
XMLNodeList const & tchildren ( tnode - > children ( ) ) ;
drop_triggers ( ) ;
{
Glib : : Threads : : RWLock : : WriterLock lm ( trigger_lock ) ;
for ( XMLNodeList : : const_iterator t = tchildren . begin ( ) ; t ! = tchildren . end ( ) ; + + t ) {
Trigger * trig ;
if ( _data_type = = DataType : : AUDIO ) {
trig = new AudioTrigger ( all_triggers . size ( ) , * this ) ;
all_triggers . push_back ( trig ) ;
trig - > set_state ( * * t , version ) ;
2021-10-29 18:22:55 -04:00
} else if ( _data_type = = DataType : : MIDI ) {
trig = new MIDITrigger ( all_triggers . size ( ) , * this ) ;
all_triggers . push_back ( trig ) ;
trig - > set_state ( * * t , version ) ;
2021-08-31 21:03:32 -04:00
}
}
}
2021-10-08 15:37:29 -04:00
XMLNode * scnode = node . child ( X_ ( " Sidechain " ) ) ;
if ( scnode ) {
2021-10-10 19:35:15 -04:00
std : : string name ;
scnode - > get_property ( X_ ( " name " ) , name ) ;
add_midi_sidechain ( name ) ;
2021-10-08 15:37:29 -04:00
assert ( _sidechain ) ;
_sidechain - > set_state ( * scnode - > children ( ) . front ( ) , version ) ;
}
2021-08-08 21:08:16 -04:00
return 0 ;
}
2021-10-07 10:03:46 -04:00
void
TriggerBox : : tempo_map_change ( )
{
Glib : : Threads : : RWLock : : ReaderLock lm ( trigger_lock ) ;
for ( auto & t : all_triggers ) {
t - > tempo_map_change ( ) ;
}
}
2021-10-09 18:58:58 -04:00
void
TriggerBox : : parameter_changed ( std : : string const & param )
{
if ( param = = X_ ( " default-trigger-input-port " ) ) {
reconnect_to_default ( ) ;
}
}
void
TriggerBox : : reconnect_to_default ( )
{
if ( ! _sidechain ) {
return ;
}
_sidechain - > input ( ) - > nth ( 0 ) - > disconnect_all ( ) ;
_sidechain - > input ( ) - > nth ( 0 ) - > connect ( Config - > get_default_trigger_input_port ( ) ) ;
}
2021-11-04 13:16:22 -04:00
MultiAllocSingleReleasePool * TriggerBox : : Request : : pool ;
void
TriggerBox : : init_pool ( )
{
/* "indirection" is because the Request struct is private, and so
nobody else can call its : : init_pool ( ) static method .
*/
Request : : init_pool ( ) ;
}
void
TriggerBox : : Request : : init_pool ( )
{
pool = new MultiAllocSingleReleasePool ( X_ ( " TriggerBoxRequests " ) , sizeof ( TriggerBox : : Request ) , 1024 ) ;
}
void *
TriggerBox : : Request : : operator new ( size_t )
{
return pool - > alloc ( ) ;
}
void
TriggerBox : : Request : : operator delete ( void * ptr , size_t /*size*/ )
{
return pool - > release ( ptr ) ;
}
void
TriggerBox : : request_reload ( int32_t slot )
{
Request * r = new Request ( Request : : Use ) ;
r - > slot = slot ;
requests . write ( & r , 1 ) ;
}
void
TriggerBox : : request_use ( int32_t slot , Trigger & t )
{
Request * r = new Request ( Request : : Use ) ;
r - > slot = slot ;
r - > trigger = & t ;
requests . write ( & r , 1 ) ;
}
void
TriggerBox : : process_requests ( )
{
Request * r ;
while ( requests . read ( & r , 1 ) = = 1 ) {
process_request ( r ) ;
}
}
void
TriggerBox : : process_request ( Request * req )
{
switch ( req - > type ) {
case Request : : Use :
break ;
case Request : : Reload :
break ;
}
delete req ; /* back to the pool, RT-safe */
}