2021-11-04 16:00:16 -04:00
# include <algorithm>
2021-07-17 15:21:45 -04:00
# include <iostream>
2021-08-10 22:35:39 -04:00
# include <cstdlib>
2022-02-07 17:44:08 -05:00
# include <memory>
2021-12-11 23:05:22 -05:00
# include <sstream>
2021-08-13 18:08:17 -04:00
2021-12-21 15:37:47 -05:00
# include <boost/make_shared.hpp>
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-11-09 23:47:15 -05:00
# include "pbd/pthread_utils.h"
2021-08-31 18:46:19 -04:00
# include "pbd/types_convert.h"
2022-02-11 12:16:04 -05:00
# include "pbd/unwind.h"
2021-07-19 11:57:31 -04:00
2021-08-05 17:03:28 -04:00
# include "temporal/tempo.h"
2022-02-24 15:38:58 -05:00
# include "ardour/auditioner.h"
2021-11-11 17:10:33 -05:00
# include "ardour/audioengine.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-12-15 20:18:18 -05:00
# include "ardour/import_status.h"
2021-07-17 15:21:45 -04:00
# include "ardour/midi_buffer.h"
2021-11-04 16:00:16 -04:00
# include "ardour/midi_model.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-12-18 11:31:44 -05:00
# include "ardour/smf_source.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 {
PBD : : PropertyDescriptor < bool > running ;
2021-09-05 12:40:58 -04:00
PBD : : PropertyDescriptor < bool > legato ;
2022-01-16 11:54:17 -05:00
PBD : : PropertyDescriptor < bool > use_follow_length ;
2021-12-06 11:26:18 -05:00
PBD : : PropertyDescriptor < Temporal : : BBT_Offset > quantization ;
2022-01-07 12:43:30 -05:00
PBD : : PropertyDescriptor < Temporal : : BBT_Offset > follow_length ;
2021-10-01 20:39:06 -04:00
PBD : : PropertyDescriptor < Trigger : : LaunchStyle > launch_style ;
2022-01-19 17:50:32 -05:00
PBD : : PropertyDescriptor < ARDOUR : : FollowAction > follow_action0 ;
PBD : : PropertyDescriptor < ARDOUR : : FollowAction > follow_action1 ;
2021-12-21 15:37:47 -05:00
PBD : : PropertyDescriptor < uint32_t > currently_playing ;
2021-12-17 17:26:07 -05:00
PBD : : PropertyDescriptor < uint32_t > follow_count ;
2021-12-06 12:27:59 -05:00
PBD : : PropertyDescriptor < int > follow_action_probability ;
2021-12-06 10:47:29 -05:00
PBD : : PropertyDescriptor < float > velocity_effect ;
PBD : : PropertyDescriptor < gain_t > gain ;
2021-12-15 19:06:25 -05:00
PBD : : PropertyDescriptor < bool > stretchable ;
2022-01-26 12:11:27 -05:00
PBD : : PropertyDescriptor < bool > cue_isolated ;
2022-02-28 10:50:46 -05:00
PBD : : PropertyDescriptor < bool > allow_patch_changes ;
2022-01-11 17:27:03 -05:00
PBD : : PropertyDescriptor < Trigger : : StretchMode > stretch_mode ;
2022-01-14 00:19:01 -05:00
PBD : : PropertyDescriptor < bool > tempo_meter ; /* only to transmit updates, not storage */
2022-02-02 14:45:57 -05:00
PBD : : PropertyDescriptor < bool > patch_change ; /* only to transmit updates, not storage */
2022-02-03 15:07:04 -05:00
PBD : : PropertyDescriptor < bool > channel_map ; /* only to transmit updates, not storage */
2022-02-24 15:38:58 -05:00
PBD : : PropertyDescriptor < bool > used_channels ; /* only to transmit updates, not storage */
2021-08-07 18:20:36 -04:00
}
}
2022-03-15 09:53:18 -04:00
PropertyChange
TriggerBox : : all_trigger_props ( )
{
PropertyChange all ;
all . add ( Properties : : name ) ;
all . add ( Properties : : color ) ;
all . add ( Properties : : legato ) ;
all . add ( Properties : : use_follow_length ) ;
all . add ( Properties : : quantization ) ;
all . add ( Properties : : follow_length ) ;
all . add ( Properties : : launch_style ) ;
all . add ( Properties : : follow_action0 ) ;
all . add ( Properties : : follow_action1 ) ;
all . add ( Properties : : follow_action_probability ) ;
all . add ( Properties : : velocity_effect ) ;
all . add ( Properties : : gain ) ;
all . add ( Properties : : stretchable ) ;
all . add ( Properties : : cue_isolated ) ;
all . add ( Properties : : allow_patch_changes ) ;
all . add ( Properties : : stretch_mode ) ;
all . add ( Properties : : tempo_meter ) ;
all . add ( Properties : : stretchable ) ;
all . add ( Properties : : patch_change ) ;
all . add ( Properties : : channel_map ) ;
all . add ( Properties : : used_channels ) ;
return all ;
}
2022-02-11 13:33:27 -05:00
std : : string
ARDOUR : : cue_marker_name ( int32_t index )
{
/* this somewhat weird code structure is intended to allow for easy and
* correct translation .
*/
using std : : string ;
2022-02-11 15:36:58 -05:00
if ( index = = INT32_MAX ) {
/* this is a reasonable "stop" icon */
return string ( X_ ( " \u25a1 " ) ) ;
}
2022-02-11 13:33:27 -05:00
switch ( index ) {
case 0 : return string ( _ ( " A " ) ) ;
case 1 : return string ( _ ( " B " ) ) ;
case 2 : return string ( _ ( " C " ) ) ;
case 3 : return string ( _ ( " D " ) ) ;
case 4 : return string ( _ ( " E " ) ) ;
case 5 : return string ( _ ( " F " ) ) ;
case 6 : return string ( _ ( " G " ) ) ;
case 7 : return string ( _ ( " H " ) ) ;
case 8 : return string ( _ ( " I " ) ) ;
case 9 : return string ( _ ( " J " ) ) ;
case 10 : return string ( _ ( " K " ) ) ;
case 11 : return string ( _ ( " L " ) ) ;
case 12 : return string ( _ ( " M " ) ) ;
case 13 : return string ( _ ( " N " ) ) ;
case 14 : return string ( _ ( " O " ) ) ;
case 15 : return string ( _ ( " P " ) ) ;
case 16 : return string ( _ ( " Q " ) ) ;
case 17 : return string ( _ ( " R " ) ) ;
case 18 : return string ( _ ( " S " ) ) ;
case 19 : return string ( _ ( " T " ) ) ;
case 20 : return string ( _ ( " U " ) ) ;
case 21 : return string ( _ ( " V " ) ) ;
case 22 : return string ( _ ( " W " ) ) ;
case 23 : return string ( _ ( " X " ) ) ;
case 24 : return string ( _ ( " Y " ) ) ;
case 25 : return string ( _ ( " Z " ) ) ;
}
return string ( ) ;
}
2022-01-19 17:50:32 -05:00
FollowAction : : FollowAction ( std : : string const & str )
{
std : : string : : size_type colon = str . find_first_of ( ' : ' ) ;
if ( colon = = std : : string : : npos ) {
throw failed_constructor ( ) ;
}
type = FollowAction : : Type ( string_2_enum ( str . substr ( 0 , colon ) , type ) ) ;
/* We use the ulong representation of the bitset because the string
version is absurd .
*/
unsigned long ul ;
std : : stringstream ss ( str . substr ( colon + 1 ) ) ;
ss > > ul ;
if ( ! ss ) {
throw failed_constructor ( ) ;
}
targets = Targets ( ul ) ;
}
std : : string
FollowAction : : to_string ( ) const
{
/* We use the ulong representation of the bitset because the string
version is absurd .
*/
return string_compose ( " %1:%2 " , enum_2_string ( type ) , targets . to_ulong ( ) ) ;
}
2021-12-27 14:04:21 -05:00
Trigger * const Trigger : : MagicClearPointerValue = ( Trigger * ) 0xfeedface ;
2021-12-22 00:35:59 -05:00
2021-12-21 15:37:47 -05:00
Trigger : : Trigger ( uint32_t n , TriggerBox & b )
2022-01-26 12:11:27 -05:00
: _launch_style ( Properties : : launch_style , OneShot )
2022-01-19 17:50:32 -05:00
, _follow_action0 ( Properties : : follow_action0 , FollowAction ( FollowAction : : Again ) )
, _follow_action1 ( Properties : : follow_action1 , FollowAction ( FollowAction : : Stop ) )
2021-12-28 09:14:50 -05:00
, _follow_action_probability ( Properties : : follow_action_probability , 0 )
2021-12-14 10:32:06 -05:00
, _follow_count ( Properties : : follow_count , 1 )
2022-01-07 09:46:51 -05:00
, _quantization ( Properties : : quantization , Temporal : : BBT_Offset ( 1 , 0 , 0 ) )
2022-01-16 11:54:17 -05:00
, _follow_length ( Properties : : follow_length , Temporal : : BBT_Offset ( 1 , 0 , 0 ) )
, _use_follow_length ( Properties : : use_follow_length , false )
2021-10-14 15:11:08 -04:00
, _legato ( Properties : : legato , false )
2021-12-17 13:40:05 -05:00
, _gain ( Properties : : gain , 1.0 )
2022-01-26 12:11:27 -05:00
, _velocity_effect ( Properties : : velocity_effect , 0. )
2021-12-15 19:06:25 -05:00
, _stretchable ( Properties : : stretchable , true )
2022-01-26 12:11:27 -05:00
, _cue_isolated ( Properties : : cue_isolated , false )
2022-02-28 10:50:46 -05:00
, _allow_patch_changes ( Properties : : allow_patch_changes , true )
2022-01-11 17:27:03 -05:00
, _stretch_mode ( Properties : : stretch_mode , Trigger : : Crisp )
2022-01-25 20:06:42 -05:00
, _name ( Properties : : name , " " )
, _color ( Properties : : color , 0xBEBEBEFF )
2022-02-14 11:52:43 -05:00
, process_index ( 0 )
, final_processed_sample ( 0 )
2022-01-26 12:11:27 -05:00
, _box ( b )
, _state ( Stopped )
2022-02-18 14:49:15 -05:00
, _playout ( false )
2022-01-26 12:11:27 -05:00
, _bang ( 0 )
, _unbang ( 0 )
, _index ( n )
, _loop_cnt ( 0 )
, _ui ( 0 )
, _explicitly_stopped ( false )
, _pending_velocity_gain ( 1.0 )
, _velocity_gain ( 1.0 )
2022-02-11 15:36:58 -05:00
, _cue_launched ( false )
2022-03-20 16:19:00 -04:00
, _used_channels ( Evoral : : SMF : : UsedChannels ( ) )
2022-01-14 00:19:01 -05:00
, _estimated_tempo ( 0. )
, _segment_tempo ( 0. )
2022-01-26 11:17:30 -05:00
, _beatcnt ( 0. )
2022-01-13 17:48:44 -05:00
, _meter ( 4 , 4 )
2021-12-18 16:11:54 -05:00
, expected_end_sample ( 0 )
2021-12-21 19:36:39 -05:00
, _pending ( ( Trigger * ) 0 )
2022-02-09 18:30:02 -05:00
, last_property_generation ( 0 )
2021-08-07 18:20:36 -04:00
{
2021-12-18 16:11:54 -05:00
add_property ( _launch_style ) ;
add_property ( _follow_action0 ) ;
add_property ( _follow_action1 ) ;
2021-12-14 14:09:43 -05:00
add_property ( _follow_action_probability ) ;
2021-12-18 16:11:54 -05:00
add_property ( _follow_count ) ;
add_property ( _quantization ) ;
2022-01-12 16:12:15 -05:00
add_property ( _follow_length ) ;
2022-01-16 11:54:17 -05:00
add_property ( _use_follow_length ) ;
2021-12-18 16:11:54 -05:00
add_property ( _legato ) ;
add_property ( _name ) ;
2021-12-17 13:40:05 -05:00
add_property ( _gain ) ;
2022-01-26 12:11:27 -05:00
add_property ( _velocity_effect ) ;
2021-12-15 19:06:25 -05:00
add_property ( _stretchable ) ;
2022-02-28 10:50:46 -05:00
add_property ( _allow_patch_changes ) ;
2022-01-25 20:06:42 -05:00
add_property ( _cue_isolated ) ;
2021-12-27 12:40:09 -05:00
add_property ( _color ) ;
2022-01-11 17:27:03 -05:00
add_property ( _stretch_mode ) ;
2022-01-25 20:06:42 -05:00
copy_to_ui_state ( ) ;
2021-09-26 23:59:15 -04:00
}
2021-12-24 16:12:13 -05:00
void
Trigger : : request_trigger_delete ( Trigger * t )
{
TriggerBox : : worker - > request_delete_trigger ( t ) ;
}
2022-02-11 08:31:03 -05:00
void
2022-02-18 20:02:56 -05:00
Trigger : : get_ui_state ( Trigger : : UIState & state ) const
2022-02-11 08:31:03 -05:00
{
/* this is used for operations like d&d when we want to query the current state */
/* you can't return ui_state here because that struct is used to queue properties that are being input *to* the trigger */
/* TODO: rename our member variable ui_state to _queued_ui_state or similar @paul ? */
state . launch_style = _launch_style ;
state . follow_action0 = _follow_action0 ;
state . follow_action1 = _follow_action1 ;
state . follow_action_probability = _follow_action_probability ;
state . follow_count = _follow_count ;
state . quantization = _quantization ;
state . follow_length = _follow_length ;
state . use_follow_length = _use_follow_length ;
state . legato = _legato ;
state . gain = _gain ;
state . velocity_effect = _velocity_effect ;
state . stretchable = _stretchable ;
2022-02-28 10:50:46 -05:00
state . allow_patch_changes = _allow_patch_changes ;
2022-02-11 08:31:03 -05:00
state . cue_isolated = _cue_isolated ;
state . stretch_mode = _stretch_mode ;
state . name = _name ;
state . color = _color ;
2022-02-24 15:38:58 -05:00
state . used_channels = used_channels ( ) ;
for ( int i = 0 ; i < 16 ; i + + ) {
2022-03-20 16:20:16 -04:00
if ( _patch_change [ i ] . is_set ( ) ) {
state . patch_change [ i ] = _patch_change [ i ] ;
2022-02-26 09:41:18 -05:00
}
2022-02-24 15:38:58 -05:00
}
2022-02-11 08:31:03 -05:00
/* tempo is currently not a property */
state . tempo = segment_tempo ( ) ;
}
void
Trigger : : set_ui_state ( Trigger : : UIState & state )
{
ui_state = state ;
/* increment ui_state generation so vals will get loaded when the trigger stops */
unsigned int g = ui_state . generation . load ( ) ;
while ( ! ui_state . generation . compare_exchange_strong ( g , g + 1 ) ) ;
/* tempo is currently outside the scope of ui_state */
if ( state . tempo > 0 ) {
set_segment_tempo ( state . tempo ) ;
}
}
2022-02-18 20:02:56 -05:00
2022-01-25 20:06:42 -05:00
void
2022-02-09 18:30:02 -05:00
Trigger : : update_properties ( )
2022-01-25 20:06:42 -05:00
{
2022-02-09 18:30:02 -05:00
/* Don't update unless there is evidence of a change */
2022-01-25 20:06:42 -05:00
2022-02-09 18:30:02 -05:00
unsigned int g ;
2022-01-25 20:06:42 -05:00
2022-02-09 18:30:02 -05:00
while ( ( g = ui_state . generation . load ( ) ) ! = last_property_generation ) {
2022-01-25 20:06:42 -05:00
2022-02-12 12:12:08 -05:00
StretchMode old_stretch = _stretch_mode ;
2022-02-09 18:30:02 -05:00
_launch_style = ui_state . launch_style ;
_follow_action0 = ui_state . follow_action0 ;
_follow_action1 = ui_state . follow_action1 ;
_follow_action_probability = ui_state . follow_action_probability ;
_follow_count = ui_state . follow_count ;
_quantization = ui_state . quantization ;
_follow_length = ui_state . follow_length ;
_use_follow_length = ui_state . use_follow_length ;
_legato = ui_state . legato ;
_gain = ui_state . gain ;
_velocity_effect = ui_state . velocity_effect ;
_stretchable = ui_state . stretchable ;
2022-02-28 10:50:46 -05:00
_allow_patch_changes = ui_state . allow_patch_changes ;
2022-02-09 18:30:02 -05:00
_cue_isolated = ui_state . cue_isolated ;
_stretch_mode = ui_state . stretch_mode ;
2022-02-11 08:31:03 -05:00
_color = ui_state . color ;
2022-02-12 12:12:08 -05:00
/* @paul: is this safe to do here ?*/
/* the UI only allows changing stretch_mode when the clip is stopped,
* and you can ' t d + d or create a new clip while it ' s playing , so I think it ' s OK */
if ( _stretch_mode ! = old_stretch ) {
setup_stretcher ( ) ;
}
2022-02-11 08:31:03 -05:00
/* during construction of a new trigger, the ui_state.name is initialized and queued
2022-03-20 11:19:28 -04:00
* . . . but in the interim , we have likely been assigned a name in a separate thread ( importing the region )
2022-02-11 08:31:03 -05:00
* . . . so don ' t overwrite our name if ui_state . name is empty
*/
if ( ui_state . name ! = " " ) {
_name = ui_state . name ;
}
2022-01-25 20:06:42 -05:00
2022-03-20 16:19:00 -04:00
_used_channels = ui_state . used_channels ;
2022-02-24 15:38:58 -05:00
for ( int chan = 0 ; chan < 16 ; chan + + ) {
if ( ui_state . patch_change [ chan ] . is_set ( ) ) {
2022-03-20 16:20:16 -04:00
_patch_change [ chan ] = ui_state . patch_change [ chan ] ;
2022-02-24 15:38:58 -05:00
}
}
2022-02-09 18:30:02 -05:00
last_property_generation = g ;
}
/* we get here when we were able to copy the entire set of properties
* without the ui_state . generation value changing during the copy , or
* when no update appeared to be required .
*/
2022-01-25 20:06:42 -05:00
}
void
Trigger : : copy_to_ui_state ( )
{
/* usable only at object creation */
ui_state . launch_style = _launch_style ;
ui_state . follow_action0 = _follow_action0 ;
ui_state . follow_action1 = _follow_action1 ;
ui_state . follow_action_probability = _follow_action_probability ;
ui_state . follow_count = _follow_count ;
ui_state . quantization = _quantization ;
ui_state . follow_length = _follow_length ;
ui_state . use_follow_length = _use_follow_length ;
ui_state . legato = _legato ;
ui_state . gain = _gain ;
2022-01-26 12:11:27 -05:00
ui_state . velocity_effect = _velocity_effect ;
2022-01-25 20:06:42 -05:00
ui_state . stretchable = _stretchable ;
ui_state . cue_isolated = _cue_isolated ;
2022-02-28 10:50:46 -05:00
ui_state . allow_patch_changes = _allow_patch_changes ;
2022-01-25 20:06:42 -05:00
ui_state . stretch_mode = _stretch_mode ;
2022-02-11 08:31:03 -05:00
ui_state . name = _name ;
ui_state . color = _color ;
2022-02-24 15:38:58 -05:00
2022-03-20 16:19:00 -04:00
ui_state . used_channels = _used_channels ;
2022-02-24 15:38:58 -05:00
for ( int i = 0 ; i < 16 ; i + + ) {
2022-03-20 16:20:16 -04:00
if ( _patch_change [ i ] . is_set ( ) ) {
ui_state . patch_change [ i ] = _patch_change [ i ] ;
2022-02-26 09:41:18 -05:00
}
2022-02-24 15:38:58 -05:00
}
2022-01-25 20:06:42 -05:00
}
2022-02-11 12:16:04 -05:00
void
Trigger : : send_property_change ( PropertyChange pc )
{
if ( _box . fast_forwarding ( ) ) {
return ;
}
2022-02-11 15:36:58 -05:00
2022-02-11 12:21:48 -05:00
PropertyChanged ( pc ) ;
2022-02-11 12:16:04 -05:00
}
2021-12-21 19:36:39 -05:00
void
Trigger : : set_pending ( Trigger * t )
{
Trigger * old = _pending . exchange ( t ) ;
2021-12-22 00:35:59 -05:00
if ( old & & old ! = MagicClearPointerValue ) {
2021-12-21 19:36:39 -05:00
/* new pending trigger set before existing pending trigger was used */
delete old ;
}
}
Trigger *
Trigger : : swap_pending ( Trigger * t )
{
return _pending . exchange ( t ) ;
}
2022-01-14 19:31:58 -05:00
bool
Trigger : : will_not_follow ( ) const
2021-09-26 23:59:15 -04:00
{
2022-01-19 17:50:32 -05:00
return ( _follow_action0 . val ( ) . type = = FollowAction : : None & & _follow_action_probability = = 0 ) | |
( _follow_action0 . val ( ) . type = = FollowAction : : None & & _follow_action1 . val ( ) . type = = FollowAction : : None ) ;
2021-08-07 18:20:36 -04:00
}
2022-01-26 12:11:27 -05:00
# define TRIGGER_UI_SET(name,type) \
2022-01-25 20:06:42 -05:00
void \
Trigger : : set_ # # name ( type val ) \
{ \
2022-01-26 12:11:27 -05:00
unsigned int g = ui_state . generation . load ( ) ; \
2022-01-25 20:06:42 -05:00
do { ui_state . name = val ; } while ( ! ui_state . generation . compare_exchange_strong ( g , g + 1 ) ) ; \
2022-02-09 19:24:08 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " trigger %1 property& cas-set: %2 gen %3 \n " , index ( ) , _ # # name . property_name ( ) , ui_state . generation . load ( ) ) ) ; \
2022-02-11 12:16:04 -05:00
send_property_change ( Properties : : name ) ; /* EMIT SIGNAL */ \
2022-01-26 12:11:27 -05:00
_box . session ( ) . set_dirty ( ) ; \
} \
type \
Trigger : : name ( ) const \
{ \
unsigned int g = ui_state . generation . load ( ) ; \
type val ; \
\
do { val = ui_state . name ; } while ( ui_state . generation . load ( ) ! = g ) ; \
\
return val ; \
2021-08-31 13:53:24 -04:00
}
2022-01-26 12:11:27 -05:00
# define TRIGGER_UI_SET_CONST_REF(name,type) \
void \
Trigger : : set_ # # name ( type const & val ) \
{ \
unsigned int g = ui_state . generation . load ( ) ; \
do { ui_state . name = val ; } while ( ! ui_state . generation . compare_exchange_strong ( g , g + 1 ) ) ; \
2022-02-09 19:24:08 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " trigger %1 property& cas-set: %2 gen %3 \n " , index ( ) , _ # # name . property_name ( ) , ui_state . generation . load ( ) ) ) ; \
2022-02-11 12:16:04 -05:00
send_property_change ( Properties : : name ) ; /* EMIT SIGNAL */ \
2022-01-26 12:11:27 -05:00
_box . session ( ) . set_dirty ( ) ; \
} \
type \
Trigger : : name ( ) const \
{ \
unsigned int g = ui_state . generation . load ( ) ; \
type val ; \
\
do { val = ui_state . name ; } while ( ui_state . generation . load ( ) ! = g ) ; \
\
return val ; \
}
2022-03-20 11:19:28 -04:00
/* these params are central to the triggerbox behavior and must only be applied at ::retrigger() via ::update_properties() */
2022-01-26 12:11:27 -05:00
TRIGGER_UI_SET ( cue_isolated , bool )
TRIGGER_UI_SET ( stretchable , bool )
TRIGGER_UI_SET ( velocity_effect , float )
TRIGGER_UI_SET ( follow_count , uint32_t )
TRIGGER_UI_SET_CONST_REF ( follow_action0 , FollowAction )
TRIGGER_UI_SET_CONST_REF ( follow_action1 , FollowAction )
TRIGGER_UI_SET ( launch_style , Trigger : : LaunchStyle )
TRIGGER_UI_SET_CONST_REF ( follow_length , Temporal : : BBT_Offset )
TRIGGER_UI_SET ( use_follow_length , bool )
TRIGGER_UI_SET ( legato , bool )
TRIGGER_UI_SET ( follow_action_probability , int )
TRIGGER_UI_SET_CONST_REF ( quantization , Temporal : : BBT_Offset )
# define TRIGGER_DIRECT_SET(name,type) \
2022-01-25 20:06:42 -05:00
void \
Trigger : : set_ # # name ( type val ) \
{ \
if ( _ # # name = = val ) { return ; } \
_ # # name = val ; \
2022-03-20 10:40:20 -04:00
ui_state . name = val ; \
unsigned int g = ui_state . generation . load ( ) ; \
do { ui_state . name = val ; } while ( ! ui_state . generation . compare_exchange_strong ( g , g + 1 ) ) ; \
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " trigger %1 property& cas-set: %2 gen %3 \n " , index ( ) , _ # # name . property_name ( ) , ui_state . generation . load ( ) ) ) ; \
2022-02-11 12:16:04 -05:00
send_property_change ( Properties : : name ) ; /* EMIT SIGNAL */ \
2022-01-25 20:06:42 -05:00
_box . session ( ) . set_dirty ( ) ; \
2022-01-26 12:11:27 -05:00
} \
type \
Trigger : : name ( ) const \
{ \
return _ # # name ; \
2021-12-27 12:40:09 -05:00
}
2022-01-26 12:11:27 -05:00
# define TRIGGER_DIRECT_SET_CONST_REF(name,type) \
void \
Trigger : : set_ # # name ( type const & val ) \
{ \
if ( _ # # name = = val ) { return ; } \
_ # # name = val ; \
2022-03-20 10:40:20 -04:00
ui_state . name = val ; \
unsigned int g = ui_state . generation . load ( ) ; \
do { ui_state . name = val ; } while ( ! ui_state . generation . compare_exchange_strong ( g , g + 1 ) ) ; \
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " trigger %1 property& cas-set: %2 gen %3 \n " , index ( ) , _ # # name . property_name ( ) , ui_state . generation . load ( ) ) ) ; \
2022-02-11 12:16:04 -05:00
send_property_change ( Properties : : name ) ; /* EMIT SIGNAL */ \
2022-01-26 12:11:27 -05:00
_box . session ( ) . set_dirty ( ) ; \
} \
type \
Trigger : : name ( ) const \
{ \
return _ # # name ; \
}
2022-01-16 11:56:45 -05:00
2022-03-20 11:19:28 -04:00
/* these params can take effect outside the scope of ::retrigger
* BUT they still need to set the ui_state variables as well as the associated member variable
* otherwise an incoming ui_state change will overwrite your changes
* */
2022-01-26 12:11:27 -05:00
TRIGGER_DIRECT_SET_CONST_REF ( name , std : : string )
TRIGGER_DIRECT_SET ( color , color_t )
2022-03-20 10:40:20 -04:00
TRIGGER_DIRECT_SET ( gain , gain_t )
2022-03-20 11:04:35 -04:00
TRIGGER_DIRECT_SET ( allow_patch_changes , bool )
2022-03-20 11:19:28 -04:00
/* patch_change[] is implemented manually but it needs to operate the same as above */
2021-12-17 13:39:38 -05:00
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-08-08 21:08:16 -04:00
XMLNode &
2022-04-06 23:56:32 -04:00
Trigger : : get_state ( ) const
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
2022-01-26 12:11:27 -05:00
/* XXX possible locking problems here if trigger is active, because
* properties could be overwritten
*/
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_ ( " index " ) , _index ) ;
2022-02-26 09:45:14 -05:00
2022-01-14 00:19:01 -05:00
node - > set_property ( X_ ( " segment-tempo " ) , _segment_tempo ) ;
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-12-23 16:51:52 -05:00
/* Set region first since set_region_in_worker_thread() will set some
values that may / will need to be overridden by XML
*/
2021-08-31 20:36:16 -04:00
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 ) {
2022-02-26 09:45:14 -05:00
set_region ( r , false ) ; //this results in a call to estimate_tempo()
2021-09-01 00:35:14 -04:00
}
2022-02-12 09:58:46 -05:00
double tempo ;
2022-03-01 16:16:01 -05:00
if ( node . get_property ( X_ ( " segment-tempo " ) , tempo ) ) {
/* this is the user-selected tempo which overrides estimated_tempo */
set_segment_tempo ( tempo ) ;
}
2021-12-23 16:51:52 -05:00
node . get_property ( X_ ( " index " ) , _index ) ;
2022-01-14 00:19:01 -05:00
set_values ( node ) ;
2021-12-23 16:51:52 -05:00
2022-01-25 20:06:42 -05:00
return 0 ;
2022-01-10 16:01:20 -05:00
}
2022-01-20 16:03:40 -05:00
bool
Trigger : : internal_use_follow_length ( ) const
{
2022-01-20 16:15:25 -05:00
return ( _follow_action0 . val ( ) . type ! = FollowAction : : None ) & & _use_follow_length ;
2022-01-20 16:03:40 -05:00
}
2021-11-10 10:45:38 -05:00
void
2021-12-23 16:51:52 -05:00
Trigger : : set_region ( boost : : shared_ptr < Region > r , bool use_thread )
2021-11-10 10:45:38 -05:00
{
2021-12-22 00:35:59 -05:00
/* Called from (G)UI thread */
2021-12-21 17:15:06 -05:00
if ( ! r ) {
/* clear operation, no need to talk to the worker thread */
2021-12-27 14:04:21 -05:00
set_pending ( Trigger : : MagicClearPointerValue ) ;
2021-12-21 20:13:50 -05:00
request_stop ( ) ;
2021-12-23 16:51:52 -05:00
} else if ( use_thread ) {
2021-12-21 17:15:06 -05:00
/* load data, do analysis in another thread */
TriggerBox : : worker - > set_region ( _box , index ( ) , r ) ;
2021-12-23 16:51:52 -05:00
} else {
set_region_in_worker_thread ( r ) ;
2021-12-21 17:15:06 -05:00
}
2021-11-10 10:45:38 -05:00
}
2021-12-22 00:35:59 -05:00
void
Trigger : : clear_region ( )
{
/* Called from RT process thread */
_region . reset ( ) ;
2021-12-27 17:47:14 -05:00
set_name ( " " ) ;
2021-12-22 00:35:59 -05: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
}
2022-02-14 11:52:43 -05:00
timepos_t
Trigger : : current_pos ( ) const
{
return timepos_t ( process_index ) ;
}
double
Trigger : : position_as_fraction ( ) const
{
if ( ! active ( ) ) {
return 0.0 ;
}
return process_index / ( double ) final_processed_sample ;
}
void
Trigger : : retrigger ( )
{
process_index = 0 ;
2022-02-18 14:49:15 -05:00
_playout = false ;
2022-02-14 11:52:43 -05:00
}
2021-07-17 15:21:45 -04:00
void
2021-11-29 23:50:19 -05:00
Trigger : : request_stop ( )
2021-07-17 15:21:45 -04:00
{
2021-11-29 23:50:19 -05:00
_requests . stop = true ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 asked to stop \n " , name ( ) ) ) ;
2021-08-05 17:03:28 -04:00
}
2021-08-08 21:08:16 -04:00
void
2022-02-02 17:04:22 -05:00
Trigger : : startup ( BufferSet & bufs , pframes_t dest_offset , Temporal : : BBT_Offset const & start_quantization )
2022-01-13 14:09:23 -05:00
{
/* This is just a non-virtual wrapper with a default parameter that calls _startup() */
2022-02-02 17:04:22 -05:00
_startup ( bufs , dest_offset , start_quantization ) ;
2022-01-13 14:09:23 -05:00
}
void
2022-02-02 17:04:22 -05:00
Trigger : : _startup ( BufferSet & bufs , pframes_t dest_offset , Temporal : : BBT_Offset const & start_quantization )
2021-07-17 15:21:45 -04:00
{
2021-09-04 12:37:36 -04:00
_state = WaitingToStart ;
2022-02-18 14:49:15 -05:00
_playout = false ;
2021-10-26 19:34:06 -04:00
_loop_cnt = 0 ;
2022-01-10 20:33:05 -05:00
_velocity_gain = _pending_velocity_gain ;
2021-12-15 13:03:47 -05:00
_explicitly_stopped = false ;
2022-01-13 14:09:23 -05:00
2022-01-14 16:36:38 -05:00
if ( start_quantization = = Temporal : : BBT_Offset ( ) ) {
/* negative quantization means "do not quantize */
_start_quantization = Temporal : : BBT_Offset ( - 1 , 0 , 0 ) ;
2022-01-13 14:09:23 -05:00
} else {
_start_quantization = _quantization ;
}
retrigger ( ) ;
2021-10-26 19:34:06 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 starts up \n " , name ( ) ) ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2021-09-04 12:37:36 -04:00
}
2021-10-19 14:52:32 -04:00
void
2022-03-17 14:18:42 -04:00
Trigger : : shutdown_from_fwd ( )
2021-10-19 14:52:32 -04:00
{
_state = Stopped ;
2022-02-18 14:49:15 -05:00
_playout = false ;
2022-02-18 14:51:42 -05:00
_loop_cnt = 0 ;
2022-02-11 15:36:58 -05:00
_cue_launched = false ;
2022-01-10 20:33:05 -05:00
_pending_velocity_gain = _velocity_gain = 1.0 ;
2021-10-26 19:34:06 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 shuts down \n " , name ( ) ) ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2021-10-19 14:52:32 -04:00
}
2022-03-17 14:18:42 -04:00
void
Trigger : : shutdown ( BufferSet & /*bufs*/ , pframes_t /*dest_offset*/ )
{
shutdown_from_fwd ( ) ;
}
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-11-17 23:14:56 -05:00
/* XXX set expected_end_sample */
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 jump_start() requested state %2 \n " , index ( ) , enum_2_string ( _state ) ) ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2021-09-05 12:40:58 -04:00
}
void
2022-01-06 18:50:55 -05:00
Trigger : : jump_stop ( BufferSet & bufs , pframes_t dest_offset )
2021-09-05 12:40:58 -04:00
{
/* this is used when we start a new trigger in legato mode. We do not
wait for quantization .
*/
2022-01-06 18:50:55 -05:00
shutdown ( bufs , dest_offset ) ;
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 jump_stop() requested state %2 \n " , index ( ) , enum_2_string ( _state ) ) ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2021-10-14 23:08:19 -04:00
}
void
2021-12-15 13:03:47 -05:00
Trigger : : begin_stop ( bool explicit_stop )
2021-10-14 23:08:19 -04:00
{
/* this is used when we start a tell a currently playing trigger to
stop , but wait for quantization first .
*/
_state = WaitingToStop ;
2021-12-15 13:03:47 -05:00
_explicitly_stopped = explicit_stop ;
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 begin_stop() requested state %2 \n " , index ( ) , enum_2_string ( _state ) ) ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2021-09-05 12:40:58 -04:00
}
2022-02-20 11:20:47 -05:00
void
Trigger : : begin_switch ( TriggerPtr nxt )
{
/* this is used when we start a tell a currently playing trigger to
stop , but wait for quantization first .
*/
_state = WaitingToSwitch ;
_nxt_quantization = nxt - > _quantization ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 begin_switch() requested state %2 \n " , index ( ) , enum_2_string ( _state ) ) ) ;
send_property_change ( ARDOUR : : Properties : : running ) ;
}
2021-07-19 11:57:31 -04:00
void
2022-01-06 18:50:55 -05:00
Trigger : : process_state_requests ( BufferSet & bufs , pframes_t dest_offset )
2021-07-19 11:57:31 -04:00
{
2021-11-29 23:50:19 -05:00
bool stop = _requests . stop . exchange ( false ) ;
2021-07-19 11:57:31 -04:00
2021-11-29 23:50:19 -05:00
if ( stop ) {
2021-12-04 13:48:42 -05:00
/* This is for an immediate stop, not a quantized one */
if ( _state ! = Stopped ) {
2022-01-06 18:50:55 -05:00
shutdown ( bufs , dest_offset ) ;
2021-12-04 13:48:42 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 immediate stop implemented \n " , name ( ) ) ) ;
2021-08-10 22:35:39 -04:00
}
2021-12-04 13:48:42 -05:00
/* Don't process bang/unbang requests since we're stopping */
_bang = 0 ;
_unbang = 0 ;
return ;
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 Running :
switch ( launch_style ( ) ) {
2021-08-12 00:16:16 -04:00
case OneShot :
2022-01-03 19:45:00 -05:00
/* do nothing, just let it keep playing */
break ;
case ReTrigger :
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 ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2021-08-08 21:08:16 -04:00
break ;
case Gate :
case Toggle :
case Repeat :
2021-12-31 14:52:59 -05:00
if ( _box . active_scene ( ) > = 0 ) {
2022-01-03 19:45:00 -05:00
std : : cerr < < " should not happen, cue launching but launch_style() said " < < enum_2_string ( launch_style ( ) ) < < std : : endl ;
2021-12-31 14:52:59 -05:00
} else {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 %2 gate/toggle/repeat => %3 \n " , index ( ) , enum_2_string ( Running ) , enum_2_string ( WaitingToStop ) ) ) ;
begin_stop ( true ) ;
}
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-12-21 15:37:47 -05:00
_box . queue_explict ( index ( ) ) ;
2022-02-11 15:36:58 -05:00
_cue_launched = ( _box . active_scene ( ) > = 0 ) ;
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 :
2022-02-20 11:20:47 -05:00
case WaitingToSwitch :
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-12-15 14:26:37 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 unbanged \n " , index ( ) ) ) ;
2021-12-27 18:24:35 -05:00
switch ( _state ) {
case Running :
begin_stop ( true ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 unbanged, now in WaitingToStop \n " , index ( ) ) ) ;
break ;
case Stopped :
case Stopping : /* theoretically not possible */
case WaitingToStop :
2022-02-20 11:20:47 -05:00
case WaitingToSwitch :
2021-12-27 18:24:35 -05:00
case WaitingForRetrigger :
/* do nothing */
break ;
case WaitingToStart :
/* didn't even get started */
2022-01-06 18:50:55 -05:00
shutdown ( bufs , dest_offset ) ;
2021-12-27 18:24:35 -05: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
2022-02-07 17:44:08 -05:00
Temporal : : BBT_Time
Trigger : : compute_start ( Temporal : : TempoMap : : SharedPtr const & tmap , samplepos_t start , samplepos_t end , Temporal : : BBT_Offset const & q , samplepos_t & start_samples , bool & will_start )
2021-08-08 21:08:16 -04:00
{
2022-02-07 17:44:08 -05:00
Temporal : : Beats start_beats ( tmap - > quarters_at ( timepos_t ( start ) ) ) ;
Temporal : : Beats end_beats ( tmap - > quarters_at ( timepos_t ( end ) ) ) ;
2021-12-11 23:05:22 -05:00
2022-02-07 17:44:08 -05:00
Temporal : : BBT_Time t_bbt ;
Temporal : : Beats t_beats ;
2021-08-10 22:35:39 -04:00
2022-02-07 17:44:08 -05:00
if ( ! compute_quantized_transition ( start , start_beats , end_beats , t_bbt , t_beats , start_samples , tmap , q ) ) {
will_start = false ;
return Temporal : : BBT_Time ( ) ;
2021-08-10 22:35:39 -04:00
}
2022-02-07 17:44:08 -05:00
will_start = true ;
return t_bbt ;
}
2022-01-07 10:03:21 -05:00
2022-02-07 17:44:08 -05:00
bool
Trigger : : compute_quantized_transition ( samplepos_t start_sample , Temporal : : Beats const & start_beats , Temporal : : Beats const & end_beats ,
Temporal : : BBT_Time & t_bbt , Temporal : : Beats & t_beats , samplepos_t & t_samples ,
Temporal : : TempoMap : : SharedPtr const & tmap , Temporal : : BBT_Offset const & q )
{
2021-10-25 14:53:27 -04:00
/* XXX need to use global grid here is quantization == zero */
2021-07-19 11:57:31 -04:00
2021-12-02 13:27:18 -05:00
/* Given the value of @param start, determine, based on the
* quantization , the next time for a transition .
*/
2022-02-20 13:00:01 -05:00
Temporal : : BBT_Time possible_bbt ;
Temporal : : Beats possible_beats ;
samplepos_t possible_samples ;
2021-12-22 15:09:48 -05:00
if ( q < Temporal : : BBT_Offset ( 0 , 0 , 0 ) ) {
/* negative quantization == do not quantize */
2021-12-22 19:27:07 -05:00
2022-02-20 13:00:01 -05:00
possible_samples = start_sample ;
possible_beats = start_beats ;
possible_bbt = tmap - > bbt_at ( possible_beats ) ;
2021-12-22 15:09:48 -05:00
} else if ( q . bars = = 0 ) {
2022-02-20 13:00:01 -05:00
possible_beats = start_beats . round_up_to_multiple ( Temporal : : Beats ( q . beats , q . ticks ) ) ;
possible_bbt = tmap - > bbt_at ( possible_beats ) ;
possible_samples = tmap - > sample_at ( possible_beats ) ;
2021-08-08 21:08:16 -04:00
} else {
2022-02-20 13:00:01 -05:00
possible_bbt = tmap - > bbt_at ( timepos_t ( start_beats ) ) ;
possible_bbt = possible_bbt . round_up_to_bar ( ) ;
2022-01-07 09:47:21 -05:00
/* bars are 1-based; 'every 4 bars' means 'on bar 1, 5, 9, ...' */
2022-02-20 13:00:01 -05:00
possible_bbt . bars = 1 + ( ( possible_bbt . bars - 1 ) / q . bars * q . bars ) ;
possible_beats = tmap - > quarters_at ( possible_bbt ) ;
possible_samples = tmap - > sample_at ( possible_bbt ) ;
2021-08-08 21:08:16 -04:00
}
2021-07-19 11:57:31 -04:00
2022-03-19 17:24:41 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %6/%1 quantized with %5 transition at %2, sb %3 eb %4 \n " , index ( ) , possible_samples , start_beats , end_beats , q , _box . order ( ) ) ) ;
2021-11-29 23:50:19 -05:00
2021-12-02 13:27:18 -05:00
/* See if this time falls within the range of time given to us */
2022-02-20 13:00:01 -05:00
if ( possible_beats < start_beats | | possible_beats > end_beats ) {
2022-02-01 21:55:37 -05:00
/* transition time not reached */
2022-02-07 17:44:08 -05:00
return false ;
}
2022-02-20 13:00:01 -05:00
t_bbt = possible_bbt ;
t_beats = possible_beats ;
t_samples = possible_samples ;
2022-02-07 17:44:08 -05:00
return true ;
}
pframes_t
Trigger : : compute_next_transition ( samplepos_t start_sample , Temporal : : Beats const & start , Temporal : : Beats const & end , pframes_t nframes ,
Temporal : : BBT_Time & t_bbt , Temporal : : Beats & t_beats , samplepos_t & t_samples ,
Temporal : : TempoMap : : SharedPtr const & tmap )
{
using namespace Temporal ;
/* In these states, we are not waiting for a transition */
2022-02-18 14:49:15 -05:00
if ( _state = = Stopped | | _state = = Running | | _state = = Stopping ) {
2022-02-07 17:44:08 -05:00
/* no transition */
2022-02-01 21:55:37 -05:00
return 0 ;
}
2022-02-07 17:44:08 -05:00
BBT_Offset q ( _start_quantization ) ;
/* Clips don't stop on their own quantize; in Live they stop on the Global Quantize setting; we will choose 1 bar (Live's default) for now */
# warning when Global Quantize is implemented, use that instead of '1 bar' here
if ( _state = = WaitingToStop ) {
2022-02-20 11:20:47 -05:00
2022-02-07 17:44:08 -05:00
q = BBT_Offset ( 1 , 0 , 0 ) ;
2022-02-20 11:20:47 -05:00
} else if ( _state = = WaitingToSwitch ) {
q = _nxt_quantization ;
2022-02-07 17:44:08 -05:00
}
if ( ! compute_quantized_transition ( start_sample , start , end , t_bbt , t_beats , t_samples , tmap , q ) ) {
/* no transition */
return 0 ;
}
2022-02-01 21:55:37 -05:00
switch ( _state ) {
case WaitingToStop :
2022-02-20 11:20:47 -05:00
case WaitingToSwitch :
2022-02-01 21:55:37 -05:00
nframes = t_samples - start_sample ;
break ;
case WaitingToStart :
nframes - = std : : max ( samplepos_t ( 0 ) , t_samples - start_sample ) ;
break ;
case WaitingForRetrigger :
break ;
default :
2022-03-14 14:18:49 -04:00
fatal < < string_compose ( _ ( " programming error: %1 %2 %3 " ) , " impossible trigger state ( " , enum_2_string ( _state ) , " ) in ::adjust_nframes() " ) < < endmsg ;
2022-02-01 21:55:37 -05:00
abort ( ) ;
}
return nframes ;
}
void
Trigger : : maybe_compute_next_transition ( samplepos_t start_sample , Temporal : : Beats const & start , Temporal : : Beats const & end , pframes_t & nframes , pframes_t & dest_offset )
{
using namespace Temporal ;
2021-08-10 22:35:39 -04:00
2022-02-01 21:55:37 -05:00
/* This should never be called by a stopped trigger */
assert ( _state ! = Stopped ) ;
2021-12-02 13:27:18 -05:00
2022-02-01 21:55:37 -05:00
/* In these states, we are not waiting for a transition */
2022-02-18 14:49:15 -05:00
if ( ( _state = = Running ) | | ( _state = = Stopping ) ) {
2022-02-01 21:55:37 -05:00
/* will cover everything */
2022-01-06 15:48:23 -05:00
return ;
2022-01-06 14:07:22 -05:00
}
2021-12-02 13:27:18 -05:00
2022-02-01 21:55:37 -05:00
Temporal : : BBT_Time transition_bbt ;
TempoMap : : SharedPtr tmap ( TempoMap : : use ( ) ) ;
2022-02-07 17:44:08 -05:00
if ( ! compute_next_transition ( start_sample , start , end , nframes , transition_bbt , transition_beats , transition_samples , tmap ) ) {
2022-02-01 21:55:37 -05:00
return ;
}
pframes_t extra_offset = 0 ;
2022-03-19 17:24:41 -04:00
Temporal : : Beats elen_ignored ;
2022-02-01 21:55:37 -05:00
2022-01-06 14:07:22 -05:00
/* transition time has arrived! let's figure out what're doing:
* stopping , starting , retriggering
*/
2021-12-02 13:27:18 -05:00
2022-01-06 14:07:22 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 in range, should start/stop at %2 aka %3 \n " , index ( ) , transition_samples , transition_beats ) ) ;
2021-12-02 13:27:18 -05:00
2022-01-06 14:07:22 -05:00
switch ( _state ) {
2021-12-02 13:27:18 -05:00
2022-01-06 14:07:22 -05:00
case WaitingToStop :
2022-02-20 11:20:47 -05:00
case WaitingToSwitch :
2022-01-06 14:07:22 -05:00
_state = Stopping ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2021-12-02 13:27:18 -05:00
2022-01-06 14:07:22 -05:00
/* trigger will reach it's end somewhere within this
* process cycle , so compute the number of samples it
* should generate .
*/
2021-12-02 13:27:18 -05:00
2022-01-06 15:48:23 -05:00
nframes = transition_samples - start_sample ;
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1/%2 will stop somewhere in the middle of run(), specifically at %3 (%4) vs expected end at %5 \n " , index ( ) , name ( ) , transition_beats , transition_samples , expected_end_sample ) ) ;
2021-12-02 13:27:18 -05:00
2022-01-06 14:07:22 -05:00
/* offset within the buffer(s) for output remains
unchanged , since we will write from the first
location corresponding to start
*/
break ;
case WaitingToStart :
retrigger ( ) ;
_state = Running ;
2022-03-19 17:24:41 -04:00
( void ) compute_end ( tmap , transition_bbt , transition_samples , elen_ignored ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2022-01-06 14:07:22 -05: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-12-02 13:27:18 -05:00
2022-01-06 14:07:22 -05:00
extra_offset = std : : max ( samplepos_t ( 0 ) , transition_samples - start_sample ) ;
2021-12-02 13:27:18 -05:00
2022-01-06 15:48:23 -05:00
nframes - = extra_offset ;
dest_offset + = extra_offset ;
2022-01-30 15:37:52 -05:00
/* XXX need to silence start of buffers up to dest_offset */
2022-01-06 14:07:22 -05:00
break ;
2021-12-02 13:27:18 -05:00
2022-01-06 14:07:22 -05:00
case WaitingForRetrigger :
retrigger ( ) ;
_state = Running ;
2022-03-19 17:24:41 -04:00
( void ) compute_end ( tmap , transition_bbt , transition_samples , elen_ignored ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2021-12-02 13:27:18 -05:00
2022-01-06 14:07:22 -05:00
/* trigger is just running normally, and will fill
* buffers entirely .
*/
break ;
2021-12-02 13:27:18 -05:00
2022-01-06 14:07:22 -05:00
default :
fatal < < string_compose ( _ ( " programming error: %1 " ) , " impossible trigger state in ::maybe_compute_next_transition() " ) < < endmsg ;
abort ( ) ;
2021-08-08 21:08:16 -04:00
}
2021-07-19 11:57:31 -04:00
2022-01-06 15:48:23 -05:00
return ;
2021-08-10 22:35:39 -04:00
}
2021-12-31 19:52:07 -05:00
void
2022-01-06 18:50:55 -05:00
Trigger : : when_stopped_during_run ( BufferSet & bufs , pframes_t dest_offset )
2021-12-31 19:52:07 -05:00
{
if ( _state = = Stopped | | _state = = Stopping ) {
if ( ( _state = = Stopped ) & & ! _explicitly_stopped & & ( launch_style ( ) = = Trigger : : Gate | | launch_style ( ) = = Trigger : : Repeat ) ) {
jump_start ( ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 was stopped, repeat/gate ret \n " , index ( ) ) ) ;
} else {
if ( ( launch_style ( ) ! = Repeat ) & & ( launch_style ( ) ! = Gate ) & & ( _loop_cnt = = _follow_count ) ) {
/* have played the specified number of times, we're done */
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 loop cnt %2 satisfied, now stopped \n " , index ( ) , _follow_count ) ) ;
2022-01-06 18:50:55 -05:00
shutdown ( bufs , dest_offset ) ;
2021-12-31 19:52:07 -05:00
} else if ( _state = = Stopping ) {
/* did not reach the end of the data. Presumably
* another trigger was explicitly queued , and we
* stopped
*/
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 not at end, but ow stopped \n " , index ( ) ) ) ;
2022-01-06 18:50:55 -05:00
shutdown ( bufs , dest_offset ) ;
2021-12-31 19:52:07 -05:00
} else {
/* reached the end, but we haven't done that enough
* times yet for a follow action / stop to take
* effect . Time to get played again .
*/
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 was stopping, now waiting to retrigger, loop cnt %2 fc %3 \n " , index ( ) , _loop_cnt , _follow_count ) ) ;
/* we will "restart" at the beginning of the
next iteration of the trigger .
*/
_state = WaitingToStart ;
retrigger ( ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : running ) ;
2021-12-31 19:52:07 -05:00
}
}
}
}
2022-02-15 18:24:01 -05:00
template < typename TriggerType >
void
Trigger : : start_and_roll_to ( samplepos_t start_pos , samplepos_t end_position , TriggerType & trigger ,
pframes_t ( TriggerType : : * run_method ) ( BufferSet & bufs , samplepos_t start_sample , samplepos_t end_sample ,
Temporal : : Beats const & start_beats , Temporal : : Beats const & end_beats ,
pframes_t nframes , pframes_t dest_offset , double bpm ) )
{
const pframes_t block_size = AudioEngine : : instance ( ) - > samples_per_cycle ( ) ;
BufferSet bufs ;
/* no need to allocate any space for BufferSet because we call
audio_run < false > ( ) which is guaranteed to never use the buffers .
AudioTrigger : : _startup ( ) also does not use BufferSet ( MIDITrigger
does , and we use virtual functions so the argument list is the same
for both , even though only the MIDI case needs the BufferSet ) .
*/
startup ( bufs , 0 , _quantization ) ;
_cue_launched = true ;
samplepos_t pos = start_pos ;
Temporal : : TempoMap : : SharedPtr tmap ( Temporal : : TempoMap : : use ( ) ) ;
while ( pos < end_position ) {
pframes_t nframes = std : : min ( block_size , ( pframes_t ) ( end_position - pos ) ) ;
Temporal : : Beats start_beats = tmap - > quarters_at ( timepos_t ( pos ) ) ;
Temporal : : Beats end_beats = tmap - > quarters_at ( timepos_t ( pos + nframes ) ) ;
const double bpm = tmap - > quarters_per_minute_at ( timepos_t ( start_beats ) ) ;
pframes_t n = ( trigger . * run_method ) ( bufs , pos , pos + nframes , start_beats , end_beats , nframes , 0 , bpm ) ;
/* We could have reached the end. Check and restart, because
* TriggerBox : : fast_forward ( ) already determined that we are
* the active trigger at @ param end_position
*/
if ( _state = = Stopped ) {
retrigger ( ) ;
_state = WaitingToStart ;
_cue_launched = true ;
}
pos + = n ;
}
}
2021-12-31 19:52:07 -05:00
2021-08-08 21:08:16 -04:00
/*--------------------*/
2021-07-17 21:01:10 -04:00
2021-12-21 15:37:47 -05:00
AudioTrigger : : AudioTrigger ( uint32_t n , TriggerBox & b )
2021-08-08 21:08:16 -04:00
: Trigger ( n , b )
2021-12-18 19:34:06 -05:00
, _stretcher ( 0 )
2021-08-13 00:59:04 -04:00
, _start_offset ( 0 )
2021-12-18 19:34:06 -05:00
, read_index ( 0 )
2022-01-26 23:20:04 -05:00
, last_readable_sample ( 0 )
2021-12-18 19:34:06 -05:00
, _legato_offset ( 0 )
, retrieved ( 0 )
2021-11-29 23:50:19 -05:00
, got_stretcher_padding ( false )
, to_pad ( 0 )
, to_drop ( 0 )
2021-08-08 21:08:16 -04:00
{
}
AudioTrigger : : ~ AudioTrigger ( )
{
2021-12-17 14:37:51 -05:00
drop_data ( ) ;
2021-11-11 17:10:33 -05:00
delete _stretcher ;
2021-07-20 00:37:17 -04:00
}
2021-07-17 15:21:45 -04:00
2022-01-17 19:03:04 -05:00
void
AudioTrigger : : set_stretch_mode ( Trigger : : StretchMode sm )
{
if ( _stretch_mode = = sm ) {
return ;
}
_stretch_mode = sm ;
2022-02-11 12:16:04 -05:00
send_property_change ( Properties : : stretch_mode ) ;
2022-01-17 19:03:04 -05:00
_box . session ( ) . set_dirty ( ) ;
}
void
AudioTrigger : : set_segment_tempo ( double t )
{
2022-03-12 12:57:54 -05:00
if ( ! _region ) {
_segment_tempo = 0 ;
return ;
}
2022-03-03 11:26:46 -05:00
if ( t < = 0. ) {
/*special case: we're told the file has no defined tempo.
* this can happen from crazy user input ( 0 beat length or somesuch ) , or if estimate_tempo ( ) fails entirely
* in either case , we need to make a sensible _beatcnt , and that means we need a tempo */
const double seconds = ( double ) data . length / _box . session ( ) . sample_rate ( ) ;
double beats = ceil ( 4. * 120. * ( seconds / 60.0 ) ) ; //how many (rounded up) 16th-notes would this be at 120bpm?
beats / = 4. ; //convert to quarter notes
t = beats / ( seconds / 60 ) ; /* our operating tempo. note that _estimated_tempo probably retains the 0bpm */
}
2022-01-17 19:03:04 -05:00
if ( _segment_tempo ! = t ) {
2022-02-12 09:58:46 -05:00
_segment_tempo = t ;
/*beatcnt is a derived property from segment tempo and the file's length*/
const double seconds = ( double ) data . length / _box . session ( ) . sample_rate ( ) ;
_beatcnt = _segment_tempo * ( seconds / 60.0 ) ;
2022-04-26 10:41:53 -04:00
/*initialize follow_length to match the length of the clip */
_follow_length = Temporal : : BBT_Offset ( 0 , _beatcnt , 0 ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : tempo_meter ) ;
2022-01-17 19:03:04 -05:00
_box . session ( ) . set_dirty ( ) ;
}
2022-02-12 09:58:46 -05:00
/* TODO: once we have a Region Trimmer, this could get more complicated:
* this segment might overlap another SD ( Coverage = = Internal | Start | End )
* in which case we might be setting both SDs , or not . TBD */
2022-02-12 10:34:42 -05:00
if ( _region ) {
SegmentDescriptor segment = get_segment_descriptor ( ) ;
for ( auto & src : _region - > sources ( ) ) {
src - > set_segment_descriptor ( segment ) ;
}
2022-02-12 09:58:46 -05:00
}
2022-01-17 19:03:04 -05:00
}
void
2022-01-26 11:17:30 -05:00
AudioTrigger : : set_segment_beatcnt ( double count )
2022-01-17 19:03:04 -05:00
{
2022-02-12 09:58:46 -05:00
//given a beatcnt from the user, we use the data length to re-calc tempo internally
// ... TODO: provide a graphical trimmer to give the user control of data.length by dragging the start and end of the sample.
const double seconds = ( double ) data . length / _box . session ( ) . sample_rate ( ) ;
double tempo = count / ( seconds / 60.0 ) ;
2022-01-17 19:03:04 -05:00
2022-02-12 09:58:46 -05:00
set_segment_tempo ( tempo ) ;
2022-01-17 19:03:04 -05:00
}
2021-12-15 12:21:28 -05:00
bool
AudioTrigger : : stretching ( ) const
{
2022-01-14 00:19:01 -05:00
return ( _segment_tempo ! = .0 ) & & _stretchable ;
2021-12-15 12:21:28 -05:00
}
2021-12-12 13:20:58 -05:00
SegmentDescriptor
AudioTrigger : : get_segment_descriptor ( ) const
{
SegmentDescriptor sd ;
2022-02-12 09:58:46 -05:00
sd . set_extent ( _region - > start_sample ( ) , _region - > length_samples ( ) ) ;
2022-01-14 00:19:01 -05:00
sd . set_tempo ( Temporal : : Tempo ( _segment_tempo , 4 ) ) ;
2021-12-12 13:20:58 -05:00
return sd ;
}
2021-09-05 01:19:47 -04:00
void
2022-02-02 17:04:22 -05:00
AudioTrigger : : _startup ( BufferSet & bufs , pframes_t dest_offset , Temporal : : BBT_Offset const & start_quantization )
2021-09-05 01:19:47 -04:00
{
2022-02-02 17:04:22 -05:00
Trigger : : _startup ( bufs , dest_offset , start_quantization ) ;
2021-09-05 01:19:47 -04:00
}
2021-09-05 12:40:58 -04:00
void
AudioTrigger : : jump_start ( )
{
Trigger : : jump_start ( ) ;
retrigger ( ) ;
}
void
2022-01-06 18:50:55 -05:00
AudioTrigger : : jump_stop ( BufferSet & bufs , pframes_t dest_offset )
2021-09-05 12:40:58 -04:00
{
2022-01-06 18:50:55 -05:00
Trigger : : jump_stop ( bufs , dest_offset ) ;
2021-09-05 12:40:58 -04:00
retrigger ( ) ;
}
2021-08-31 18:46:19 -04:00
XMLNode &
2022-04-06 23:56:32 -04:00
AudioTrigger : : get_state ( ) const
2021-08-31 18:46:19 -04:00
{
XMLNode & node ( Trigger : : get_state ( ) ) ;
node . set_property ( X_ ( " start " ) , timepos_t ( _start_offset ) ) ;
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 ;
2022-02-08 20:30:10 -05:00
if ( Trigger : : set_state ( node , version ) ) {
2021-09-01 00:35:14 -04:00
return - 1 ;
}
2021-08-31 20:36:16 -04:00
node . get_property ( X_ ( " start " ) , t ) ;
_start_offset = t . samples ( ) ;
2022-02-26 09:42:41 -05:00
/* we've changed our internal values; we need to update our queued UIState or they will be lost when UIState is applied */
copy_to_ui_state ( ) ;
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
{
2022-01-26 13:27:27 -05:00
/* XXX better minimum size needed */
2022-01-26 23:20:04 -05:00
_start_offset = std : : max ( samplepos_t ( 4096 ) , s . samples ( ) ) ;
2021-08-13 00:59:04 -04:00
}
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 ( ) ;
}
2021-12-24 16:10:24 -05:00
timepos_t
AudioTrigger : : start_offset ( ) const
{
return timepos_t ( _start_offset ) ;
}
2021-08-11 20:25:46 -04:00
void
2022-02-08 19:05:15 -05:00
AudioTrigger : : start_and_roll_to ( samplepos_t start_pos , samplepos_t end_position )
2022-02-07 17:44:08 -05:00
{
2022-02-15 18:24:01 -05:00
Trigger : : start_and_roll_to < AudioTrigger > ( start_pos , end_position , * this , & AudioTrigger : : audio_run < false > ) ;
2022-02-07 17:44:08 -05:00
}
2022-02-08 18:59:36 -05:00
timepos_t
2022-03-19 17:24:41 -04:00
AudioTrigger : : compute_end ( Temporal : : TempoMap : : SharedPtr const & tmap , Temporal : : BBT_Time const & transition_bbt , samplepos_t transition_sample , Temporal : : Beats & effective_length )
2021-08-11 20:25:46 -04:00
{
2022-01-10 16:01:20 -05:00
/* Our task here is to set:
2022-01-26 23:20:04 -05:00
expected_end_sample : ( TIMELINE ! ) the sample position where the data for the clip should run out ( taking stretch into account )
last_readable_sample : the sample in the data where we stop reading
final_processed_sample : the sample where the trigger stops and the follow action if any takes effect
2022-01-10 16:01:20 -05:00
Things that affect these values :
data . length : how many samples there are in the data ( AudioTime / samples )
2022-01-16 11:54:17 -05:00
_follow_length : the ( user specified ) time after the start of the trigger when the follow action should take effect
_use_follow_length : whether to use the follow_length value , or the clip ' s natural length
2022-01-26 11:17:30 -05:00
_beatcnt : the expected duration of the trigger , based on analysis of its tempo . . can be overridden by the user later
2022-01-10 16:01:20 -05:00
*/
2022-01-16 11:54:17 -05:00
samplepos_t end_by_follow_length = tmap - > sample_at ( tmap - > bbt_walk ( transition_bbt , _follow_length ) ) ;
2022-01-26 13:27:27 -05:00
samplepos_t end_by_data_length = transition_sample + ( data . length - _start_offset ) ;
2022-03-05 13:48:52 -05:00
/* this could still blow up if the data is less than 1 tick long, but
we should handle that elsewhere .
*/
const Temporal : : Beats bc ( Temporal : : Beats : : from_double ( _beatcnt ) ) ;
samplepos_t end_by_beatcnt = tmap - > sample_at ( tmap - > bbt_walk ( transition_bbt , Temporal : : BBT_Offset ( 0 , bc . get_beats ( ) , bc . get_ticks ( ) ) ) ) ;
2022-01-10 16:01:20 -05:00
2022-01-26 23:20:04 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 SO %9 @ %2 / %3 / %4 ends: FL %5 (from %6) BC %7 DL %8 \n " ,
2022-01-14 16:39:16 -05:00
index ( ) , transition_sample , transition_beats , transition_bbt ,
2022-01-26 23:20:04 -05:00
end_by_follow_length , _follow_length , end_by_beatcnt , end_by_data_length , _start_offset ) ) ;
2022-01-10 16:01:20 -05:00
if ( stretching ( ) ) {
2022-01-20 16:03:40 -05:00
if ( internal_use_follow_length ( ) ) {
2022-01-26 11:17:30 -05:00
expected_end_sample = std : : min ( end_by_follow_length , end_by_beatcnt ) ;
2022-01-10 17:49:07 -05:00
} else {
2022-01-26 11:17:30 -05:00
expected_end_sample = end_by_beatcnt ;
2022-01-10 17:49:07 -05:00
}
2022-01-10 16:01:20 -05:00
} else {
2022-01-20 16:03:40 -05:00
if ( internal_use_follow_length ( ) ) {
2022-01-10 17:49:07 -05:00
expected_end_sample = std : : min ( end_by_follow_length , end_by_data_length ) ;
} else {
expected_end_sample = end_by_data_length ;
}
2021-08-11 20:25:46 -04:00
}
2022-01-20 16:03:40 -05:00
if ( internal_use_follow_length ( ) ) {
2022-01-26 23:20:04 -05:00
final_processed_sample = end_by_follow_length - transition_sample ;
2022-01-10 16:01:20 -05:00
} else {
2022-01-26 23:20:04 -05:00
final_processed_sample = expected_end_sample - transition_sample ;
2021-08-11 20:25:46 -04:00
}
2022-01-10 16:01:20 -05:00
samplecnt_t usable_length ;
2021-12-18 16:11:54 -05:00
2022-01-20 16:03:40 -05:00
if ( internal_use_follow_length ( ) & & ( end_by_follow_length < end_by_data_length ) ) {
2022-01-26 23:20:04 -05:00
usable_length = end_by_follow_length - transition_samples ;
2022-01-10 16:01:20 -05:00
} else {
2022-01-26 13:27:27 -05:00
usable_length = ( data . length - _start_offset ) ;
2021-08-11 20:25:46 -04:00
}
2022-02-07 17:44:08 -05:00
/* called from compute_end() when we know the time (audio &
2022-01-10 16:01:20 -05:00
* musical time domains when we start starting . Our job here is to
2022-01-26 23:20:04 -05:00
* define the last_readable_sample we can use as data .
2022-01-10 16:01:20 -05:00
*/
2022-01-11 13:32:35 -05:00
Temporal : : BBT_Offset q ( _quantization ) ;
if ( launch_style ( ) ! = Repeat | | ( q = = Temporal : : BBT_Offset ( ) ) ) {
2022-01-10 16:01:20 -05:00
2022-01-26 23:20:04 -05:00
last_readable_sample = _start_offset + usable_length ;
2022-01-10 16:01:20 -05:00
} else {
/* This is for Repeat mode only deliberately ignore the _follow_length
* here , because we ' ll be playing just the quantization distance no
* matter what .
*/
2021-08-11 20:25:46 -04:00
2022-01-10 16:01:20 -05:00
/* XXX MUST HANDLE BAR-LEVEL QUANTIZATION */
timecnt_t len ( Temporal : : Beats ( q . beats , q . ticks ) , timepos_t ( Temporal : : Beats ( ) ) ) ;
2022-01-26 23:20:04 -05:00
last_readable_sample = _start_offset + len . samples ( ) ;
2022-01-10 16:01:20 -05:00
}
2022-03-19 17:24:41 -04:00
effective_length = tmap - > quarters_at_sample ( transition_sample + final_processed_sample ) - tmap - > quarters_at_sample ( transition_sample ) ;
2022-01-26 23:20:04 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1: final sample %2 vs ees %3 ls %4 \n " , index ( ) , final_processed_sample , expected_end_sample , last_readable_sample ) ) ;
2022-02-07 17:44:08 -05:00
2022-02-08 18:59:36 -05:00
return timepos_t ( expected_end_sample ) ;
2021-08-11 20:25:46 -04:00
}
2021-11-11 17:10:33 -05:00
void
AudioTrigger : : set_length ( timecnt_t const & newlen )
{
/* XXX what? */
}
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-12-17 14:37:51 -05: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
2021-12-22 09:42:52 -05:00
AudioTrigger : : set_region_in_worker_thread ( boost : : shared_ptr < Region > r )
2021-07-19 11:57:31 -04:00
{
2021-12-17 21:22:44 -05:00
assert ( ! active ( ) ) ;
2021-11-11 17:10:33 -05:00
2021-07-19 11:57:31 -04:00
boost : : shared_ptr < AudioRegion > ar = boost : : dynamic_pointer_cast < AudioRegion > ( r ) ;
2021-12-17 21:22:44 -05:00
if ( r & & ! ar ) {
2021-07-19 11:57:31 -04:00
return - 1 ;
}
set_region_internal ( r ) ;
2021-12-17 21:22:44 -05:00
if ( ! r ) {
/* unset */
return 0 ;
}
2021-10-07 10:03:46 -04:00
load_data ( ar ) ;
2022-01-14 00:19:01 -05:00
2022-02-12 09:58:46 -05:00
estimate_tempo ( ) ; /* NOTE: if this is an existing clip (D+D copy) then it will likely have a SD tempo, and that short-circuits minibpm for us */
/* given an initial tempo guess, we need to set our operating tempo and beat_cnt value.
* this may be reset momentarily with user - settings ( UIState ) from a d + d operation */
set_segment_tempo ( _estimated_tempo ) ;
2022-01-14 00:19:01 -05:00
2021-11-11 17:10:33 -05:00
setup_stretcher ( ) ;
2021-07-19 11:57:31 -04:00
2021-12-22 15:09:48 -05:00
/* Given what we know about the tempo and duration, set the defaults
* for the trigger properties .
*/
2022-03-03 11:26:46 -05:00
if ( _estimated_tempo = = 0. ) {
2021-12-22 15:09:48 -05:00
_stretchable = false ;
2022-03-03 11:26:46 -05:00
_quantization = Temporal : : BBT_Offset ( 1 , 0 , 0 ) ;
2022-01-19 17:50:32 -05:00
_follow_action0 = FollowAction ( FollowAction : : None ) ;
2021-12-22 15:09:48 -05:00
} else {
if ( probably_oneshot ( ) ) {
/* short trigger, treat as a one shot */
_stretchable = false ;
2022-01-19 17:50:32 -05:00
_follow_action0 = FollowAction ( FollowAction : : None ) ;
2021-12-22 15:09:48 -05:00
_quantization = Temporal : : BBT_Offset ( - 1 , 0 , 0 ) ;
} else {
_stretchable = true ;
2022-01-07 09:46:51 -05:00
_quantization = Temporal : : BBT_Offset ( 1 , 0 , 0 ) ;
2022-01-19 17:50:32 -05:00
_follow_action0 = FollowAction ( FollowAction : : Again ) ;
2021-12-22 15:09:48 -05:00
}
}
2021-12-23 16:53:39 -05:00
_follow_action_probability = 0 ; /* 100% left */
2022-03-03 11:19:40 -05:00
/* we've changed our internal values; we need to update our queued UIState or they will be lost when UIState is applied */
copy_to_ui_state ( ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : name ) ;
2021-10-06 17:20:52 -04:00
2021-10-07 10:03:46 -04:00
return 0 ;
}
void
2022-01-14 00:19:01 -05:00
AudioTrigger : : estimate_tempo ( )
2021-10-07 10:03:46 -04:00
{
using namespace Temporal ;
2022-01-13 17:48:44 -05:00
TempoMap : : SharedPtr tm ( TempoMap : : use ( ) ) ;
2021-10-06 17:20:52 -04:00
2022-01-13 17:48:44 -05:00
TimelineRange range ( _region - > start ( ) , _region - > start ( ) + _region - > length ( ) , 0 ) ;
2022-01-13 18:00:03 -05:00
SegmentDescriptor segment ;
bool have_segment ;
2022-01-13 17:48:44 -05:00
2022-01-13 18:00:03 -05:00
have_segment = _region - > source ( 0 ) - > get_segment_descriptor ( range , segment ) ;
2022-01-13 17:48:44 -05:00
2022-01-13 18:00:03 -05:00
if ( have_segment ) {
2022-01-14 00:19:01 -05:00
_estimated_tempo = segment . tempo ( ) . quarter_notes_per_minute ( ) ;
2022-01-13 18:00:03 -05:00
_meter = segment . meter ( ) ;
2022-01-14 11:57:16 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1: tempo and meter from segment descriptor \n " , index ( ) ) ) ;
2022-01-13 17:48:44 -05:00
} else {
/* not a great guess, but what else can we do? */
TempoMetric const & metric ( tm - > metric_at ( timepos_t ( AudioTime ) ) ) ;
_meter = metric . meter ( ) ;
/* check the name to see if there's a (heuristically obvious) hint
* about the tempo .
*/
2021-08-06 15:03:28 -04:00
2022-01-13 17:48:44 -05:00
string str = _region - > name ( ) ;
string : : size_type bi ;
string : : size_type ni ;
double text_tempo = - 1. ;
2021-12-11 23:05:22 -05:00
2022-01-26 12:50:52 -05:00
if ( ( ( bi = str . find ( " bpm " ) ) ! = string : : npos ) | |
( ( bi = str . find ( " bpm " ) ) ! = string : : npos ) | |
( ( bi = str . find ( " BPM " ) ) ! = string : : npos ) | |
( ( bi = str . find ( " BPM " ) ) ! = string : : npos ) ) {
2021-12-11 23:05:22 -05:00
2022-01-13 17:48:44 -05:00
string sub ( str . substr ( 0 , bi ) ) ;
2021-12-11 23:05:22 -05:00
2022-01-13 17:48:44 -05:00
if ( ( ni = sub . find_last_of ( " 0123456789.,_- " ) ) ! = string : : npos ) {
2021-12-11 23:05:22 -05:00
2022-01-13 17:48:44 -05:00
int nni = ni ; /* ni is unsigned, nni is signed */
2021-12-11 23:05:22 -05:00
2022-01-13 17:48:44 -05:00
while ( nni > = 0 ) {
if ( ! isdigit ( sub [ nni ] ) & &
( sub [ nni ] ! = ' . ' ) & &
( sub [ nni ] ! = ' , ' ) ) {
break ;
}
- - nni ;
2021-12-11 23:05:22 -05:00
}
2022-01-13 17:48:44 -05:00
if ( nni > 0 ) {
std : : stringstream p ( sub . substr ( nni + 1 ) ) ;
p > > text_tempo ;
if ( ! p ) {
text_tempo = - 1. ;
} else {
2022-01-14 00:19:01 -05:00
_estimated_tempo = text_tempo ;
2022-01-13 17:48:44 -05:00
}
2021-12-11 23:05:22 -05:00
}
}
}
2021-10-06 17:20:52 -04:00
2022-01-13 17:48:44 -05:00
if ( text_tempo < 0 ) {
2021-12-11 23:05:22 -05:00
2022-01-13 17:48:44 -05:00
breakfastquay : : MiniBPM mbpm ( _box . session ( ) . sample_rate ( ) ) ;
2021-10-06 17:20:52 -04:00
2022-01-14 00:19:01 -05:00
_estimated_tempo = mbpm . estimateTempoOfSamples ( data [ 0 ] , data . length ) ;
2021-10-07 10:03:46 -04:00
2022-03-16 12:01:17 -04:00
//cerr << name() << "MiniBPM Estimated: " << _estimated_tempo << " bpm from " << (double) data.length / _box.session().sample_rate() << " seconds\n";
2022-01-14 11:57:16 -05:00
}
2021-10-06 17:20:52 -04:00
}
2021-12-17 14:37:51 -05:00
const double seconds = ( double ) data . length / _box . session ( ) . sample_rate ( ) ;
2022-01-18 11:14:31 -05:00
2021-12-15 17:39:28 -05:00
/* now check the determined tempo and force it to a value that gives us
2022-01-26 11:17:30 -05:00
an integer beat / quarter count . This is a heuristic that tries to
2021-12-15 17:39:28 -05:00
avoid clips that slightly over - or underrun a quantization point ,
resulting in small or larger gaps in output if they are repeating .
*/
2022-01-26 11:17:30 -05:00
if ( ( _estimated_tempo ! = 0. ) ) {
/* fractional beatcnt */
double maybe_beats = ( seconds / 60. ) * _estimated_tempo ;
2022-03-01 16:16:01 -05:00
double beatcount = round ( maybe_beats ) ;
2022-05-14 15:12:24 -04:00
/* the vast majority of third-party clips are 1,2,4,8, or 16-bar 'beats'.
* Given no other metadata , it makes things ' just work ' if we assume 4 / 4 time signature , and power - of - 2 bars ( 1 , 2 , 4 , 8 or 16 )
* TODO : someday we could provide a widget for users who have unlabeled , un - metadata ' d , clips that they * know * are 3 / 4 or 5 / 4 or 11 / 4 */
{
double barcount = round ( beatcount / 4 ) ;
if ( barcount < = 18 ) { /* why not 16 here? fuzzy logic allows minibpm to misjudge the clip a bit */
for ( int pwr = 0 ; pwr < = 4 ; pwr + + ) {
float bc = pow ( 2 , pwr ) ;
if ( barcount < = bc ) {
barcount = bc ;
break ;
}
}
}
beatcount = round ( barcount * 4 ) ;
}
2022-01-26 11:17:30 -05:00
double est = _estimated_tempo ;
2022-02-12 09:58:46 -05:00
_estimated_tempo = beatcount / ( seconds / 60. ) ;
2022-01-26 11:17:30 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " given original estimated tempo %1, rounded beatcnt is %2 : resulting in working bpm = %3 \n " , est , _beatcnt , _estimated_tempo ) ) ;
2021-12-15 16:58:03 -05:00
2022-03-01 16:16:01 -05:00
/* initialize our follow_length to match the beatcnt ... user can later change this value to have the clip end sooner or later than its data length */
set_follow_length ( Temporal : : BBT_Offset ( 0 , rint ( beatcount ) , 0 ) ) ;
}
2022-01-26 11:17:30 -05:00
2022-02-12 12:14:12 -05:00
#if 0
2022-03-01 16:16:01 -05:00
cerr < < " estimated tempo: " < < _estimated_tempo < < endl ;
2022-02-12 12:14:12 -05:00
const samplecnt_t one_beat = tm - > bbt_duration_at ( timepos_t ( AudioTime ) , BBT_Offset ( 0 , 1 , 0 ) ) . samples ( ) ;
2022-01-26 11:17:30 -05:00
cerr < < " one beat in samples: " < < one_beat < < endl ;
2022-02-12 09:58:46 -05:00
cerr < < " rounded beatcount = " < < round ( beatcount ) < < endl ;
2022-02-12 12:14:12 -05:00
# endif
2021-10-07 10:03:46 -04:00
}
2021-08-05 18:20:37 -04:00
2021-12-22 15:09:48 -05:00
bool
AudioTrigger : : probably_oneshot ( ) const
{
2022-01-14 00:19:01 -05:00
assert ( _segment_tempo ! = 0. ) ;
2021-12-22 15:09:48 -05:00
2022-01-26 11:17:30 -05:00
if ( ( data . length < ( _box . session ( ) . sample_rate ( ) / 2 ) ) | | //less than 1/2 second
( _segment_tempo > 140 ) | | //minibpm thinks this is really fast
( _segment_tempo < 60 ) ) { //minibpm thinks this is really slow
2021-12-22 15:09:48 -05:00
return true ;
}
return false ;
}
2021-12-15 20:15:58 -05:00
void
AudioTrigger : : io_change ( )
{
if ( _stretcher ) {
setup_stretcher ( ) ;
}
}
/* This exists so that we can play with the value easily. Currently, 1024 seems as good as any */
2021-11-29 23:50:19 -05:00
static const samplecnt_t rb_blocksize = 1024 ;
2022-02-08 22:33:25 -05:00
void
AudioTrigger : : reset_stretcher ( )
{
_stretcher - > reset ( ) ;
got_stretcher_padding = false ;
to_pad = 0 ;
to_drop = 0 ;
}
2021-10-07 10:03:46 -04:00
void
2021-11-11 17:10:33 -05:00
AudioTrigger : : setup_stretcher ( )
2021-10-07 10:03:46 -04:00
{
2021-11-11 17:10:33 -05:00
using namespace RubberBand ;
using namespace Temporal ;
2021-10-07 10:03:46 -04:00
if ( ! _region ) {
return ;
}
2021-11-11 17:10:33 -05:00
boost : : shared_ptr < AudioRegion > ar ( boost : : dynamic_pointer_cast < AudioRegion > ( _region ) ) ;
2021-12-15 20:15:58 -05:00
const uint32_t nchans = std : : min ( _box . input_streams ( ) . n_audio ( ) , ar - > n_channels ( ) ) ;
2021-11-11 17:10:33 -05:00
2022-01-11 17:27:03 -05:00
//map our internal enum to a rubberband option
RubberBandStretcher : : Option ro ;
switch ( _stretch_mode ) {
case Trigger : : Crisp : ro = RubberBandStretcher : : OptionTransientsCrisp ; break ;
case Trigger : : Mixed : ro = RubberBandStretcher : : OptionTransientsMixed ; break ;
case Trigger : : Smooth : ro = RubberBandStretcher : : OptionTransientsSmooth ; break ;
}
2021-11-11 17:10:33 -05:00
RubberBandStretcher : : Options options = RubberBandStretcher : : Option ( RubberBandStretcher : : OptionProcessRealTime |
2022-01-11 17:27:03 -05:00
ro ) ;
2021-11-11 17:10:33 -05:00
2021-12-15 20:15:58 -05:00
delete _stretcher ;
_stretcher = new RubberBandStretcher ( _box . session ( ) . sample_rate ( ) , nchans , options , 1.0 , 1.0 ) ;
2021-11-29 23:50:19 -05:00
_stretcher - > setMaxProcessSize ( rb_blocksize ) ;
2021-07-19 11:57:31 -04:00
}
void
AudioTrigger : : drop_data ( )
{
2021-12-17 14:37:51 -05:00
for ( auto & d : data ) {
delete [ ] d ;
2021-07-19 11:57:31 -04:00
}
data . clear ( ) ;
}
int
AudioTrigger : : load_data ( boost : : shared_ptr < AudioRegion > ar )
{
const uint32_t nchans = ar - > n_channels ( ) ;
2021-12-17 14:37:51 -05:00
data . length = ar - > length_samples ( ) ;
2021-07-19 11:57:31 -04:00
drop_data ( ) ;
try {
for ( uint32_t n = 0 ; n < nchans ; + + n ) {
2021-12-17 14:37:51 -05: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
{
2022-02-14 11:52:43 -05:00
Trigger : : retrigger ( ) ;
2022-01-25 20:06:42 -05:00
update_properties ( ) ;
2022-02-08 22:33:25 -05:00
reset_stretcher ( ) ;
2022-01-25 20:06:42 -05:00
2021-08-31 13:53:24 -04:00
read_index = _start_offset + _legato_offset ;
2021-11-29 23:50:19 -05:00
retrieved = 0 ;
2021-09-05 12:40:58 -04:00
_legato_offset = 0 ; /* used one time only */
2022-02-08 22:33:25 -05:00
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
}
2022-02-14 20:13:06 -05:00
template < bool in_process_context >
2021-11-29 23:50:19 -05:00
pframes_t
2022-02-07 17:44:08 -05:00
AudioTrigger : : audio_run ( BufferSet & bufs , samplepos_t start_sample , samplepos_t end_sample ,
Temporal : : Beats const & start , Temporal : : Beats const & end ,
pframes_t nframes , pframes_t dest_offset , double bpm )
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-12-15 20:15:58 -05:00
/* We do not modify the I/O of our parent route, so we process only min (bufs.n_audio(),region.channels()) */
2022-02-14 20:13:06 -05:00
const uint32_t nchans = ( in_process_context ? std : : min ( bufs . count ( ) . n_audio ( ) , ar - > n_channels ( ) ) : ar - > n_channels ( ) ) ;
2021-11-29 23:50:19 -05:00
int avail = 0 ;
2022-02-07 17:44:08 -05:00
BufferSet * scratch ;
std : : unique_ptr < BufferSet > scratchp ;
2021-12-03 09:31:45 -05:00
std : : vector < Sample * > bufp ( nchans ) ;
2022-03-03 11:22:17 -05:00
const bool do_stretch = stretching ( ) & & _segment_tempo > 1 ;
2021-08-07 20:53:49 -04:00
2021-12-02 13:27:18 -05:00
/* see if we're going to start or stop or retrigger in this run() call */
2022-01-30 15:37:52 -05:00
maybe_compute_next_transition ( start_sample , start , end , nframes , dest_offset ) ;
2022-01-06 15:48:23 -05:00
const pframes_t orig_nframes = nframes ;
2021-12-02 13:27:18 -05:00
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1/%2 after checking for transition, state = %3, will stretch %4, nf will be %5 of %6 \n " , index ( ) , name ( ) , enum_2_string ( _state ) , do_stretch , nframes ) ) ;
2021-12-02 13:27:18 -05:00
switch ( _state ) {
case Stopped :
case WaitingForRetrigger :
case WaitingToStart :
/* did everything we could do */
return nframes ;
case Running :
case WaitingToStop :
2022-02-20 11:20:47 -05:00
case WaitingToSwitch :
2021-12-02 13:27:18 -05:00
case Stopping :
/* stuff to do */
break ;
}
2021-07-17 15:21:45 -04:00
2021-11-29 23:50:19 -05:00
/* We use session scratch buffers for both padding the start of the
* input to RubberBand , and to hold the output . Because of this dual
* purpose , we use a generic variable name ( ' bufp ' ) to refer to them .
*/
2021-11-11 17:10:33 -05:00
2022-02-14 20:13:06 -05:00
if ( in_process_context ) {
2022-02-07 17:44:08 -05:00
scratch = & ( _box . session ( ) . get_scratch_buffers ( ChanCount ( DataType : : AUDIO , nchans ) ) ) ;
} else {
scratchp . reset ( new BufferSet ( ) ) ;
scratchp - > ensure_buffers ( DataType : : AUDIO , nchans , nframes ) ;
2022-02-14 20:13:06 -05:00
/* have to set up scratch as a raw ptr so that the in_process_context
and ! in_process_context case can use the same code syntax
2022-02-07 17:44:08 -05:00
*/
scratch = scratchp . get ( ) ;
2021-11-11 17:10:33 -05:00
}
2022-02-08 22:33:25 -05:00
for ( uint32_t chn = 0 ; chn < nchans ; + + chn ) {
bufp [ chn ] = scratch - > get_audio ( chn ) . data ( ) ;
2022-02-08 19:05:15 -05:00
}
2021-11-29 23:50:19 -05:00
/* tell the stretcher what we are doing for this ::run() call */
2021-11-11 17:10:33 -05:00
2022-02-18 14:49:15 -05:00
if ( do_stretch & & ! _playout ) {
2021-11-11 17:10:33 -05:00
2022-01-14 00:19:01 -05:00
const double stretch = _segment_tempo / bpm ;
2021-11-29 23:50:19 -05:00
_stretcher - > setTimeRatio ( stretch ) ;
2021-11-11 17:10:33 -05:00
2022-01-14 00:19:01 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " clip tempo %1 bpm %2 ratio %3%4 \n " , _segment_tempo , bpm , std : : setprecision ( 6 ) , stretch ) ) ;
2021-11-29 23:50:19 -05:00
if ( ( avail = _stretcher - > available ( ) ) < 0 ) {
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1/%2 stretcher->available() returned %3 - not configured! \n " , index ( ) , name ( ) , avail ) ) ;
2021-11-29 23:50:19 -05:00
error < < _ ( " Could not configure rubberband stretcher " ) < < endmsg ;
2022-01-10 16:01:20 -05:00
return 0 ;
2021-11-29 23:50:19 -05:00
}
/* We are using Rubberband in realtime mode, but this mdoe of
* operation has some issues . The first is that it will
* generate a certain number of samples of output at the start
* that are not based on the input , due to processing latency .
*
* In this context , we don ' t care about this output , because we
* have all the data available from the outset , and we can just
* wait until this " latency " period is over . So we will feed
* an initial chunk of data to the stretcher , and then throw
* away the corresponding data on the output .
*
* This code is modelled on the code for rubberband ( 1 ) , part of
* the rubberband software .
*/
if ( ! got_stretcher_padding ) {
to_pad = _stretcher - > getLatency ( ) ;
to_drop = to_pad ;
got_stretcher_padding = true ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 requires %2 padding %3 \n " , name ( ) , to_pad ) ) ;
}
while ( to_pad > 0 ) {
2022-02-07 17:44:08 -05:00
const samplecnt_t limit = std : : min ( ( samplecnt_t ) scratch - > get_audio ( 0 ) . capacity ( ) , to_pad ) ;
2021-11-29 23:50:19 -05:00
for ( uint32_t chn = 0 ; chn < nchans ; + + chn ) {
2022-02-07 17:44:08 -05:00
memset ( bufp [ chn ] , 0 , sizeof ( Sample ) * limit ) ;
2021-11-29 23:50:19 -05:00
}
2021-12-03 09:31:45 -05:00
_stretcher - > process ( & bufp [ 0 ] , limit , false ) ;
2021-11-29 23:50:19 -05:00
to_pad - = limit ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 padded %2 left %3 \n " , name ( ) , limit , to_pad ) ) ;
}
2021-11-11 17:10:33 -05:00
}
2022-02-18 14:49:15 -05:00
while ( nframes & & ! _playout ) {
2021-08-08 21:08:16 -04:00
2021-11-29 23:50:19 -05:00
pframes_t to_stretcher ;
pframes_t from_stretcher ;
2021-07-17 15:21:45 -04:00
2021-12-15 12:21:28 -05:00
if ( do_stretch ) {
2021-11-11 17:10:33 -05:00
2022-01-26 23:20:04 -05:00
if ( read_index < last_readable_sample ) {
2021-11-11 17:10:33 -05:00
2021-11-29 23:50:19 -05:00
/* still have data to push into the stretcher */
2021-11-11 17:10:33 -05:00
2022-01-26 23:20:04 -05:00
to_stretcher = ( pframes_t ) std : : min ( samplecnt_t ( rb_blocksize ) , ( last_readable_sample - read_index ) ) ;
2021-11-29 23:50:19 -05:00
const bool at_end = ( to_stretcher < rb_blocksize ) ;
2021-11-11 17:10:33 -05:00
2022-01-26 23:20:04 -05:00
while ( ( pframes_t ) avail < nframes & & ( read_index < last_readable_sample ) ) {
2021-11-29 23:50:19 -05:00
/* keep feeding the stretcher in chunks of "to_stretcher",
* until there ' s nframes of data available , or we reach
* the end of the region
*/
2021-12-03 09:31:45 -05:00
std : : vector < Sample * > in ( nchans ) ;
2021-11-29 23:50:19 -05:00
for ( uint32_t chn = 0 ; chn < nchans ; + + chn ) {
in [ chn ] = data [ chn ] + read_index ;
}
2021-11-11 17:10:33 -05:00
2021-12-15 20:18:18 -05:00
/* Note: RubberBandStretcher's process() and retrieve() API's accepts Sample**
* as their first argument . This code may appear to only be processing the first
* channel , but actually processes them all in one pass .
*/
2021-12-03 09:31:45 -05:00
_stretcher - > process ( & in [ 0 ] , to_stretcher , at_end ) ;
2021-11-29 23:50:19 -05:00
read_index + = to_stretcher ;
avail = _stretcher - > available ( ) ;
2021-11-11 17:10:33 -05:00
2021-11-29 23:50:19 -05:00
if ( to_drop & & avail ) {
2022-02-07 17:44:08 -05:00
samplecnt_t this_drop = std : : min ( std : : min ( ( samplecnt_t ) avail , to_drop ) , ( samplecnt_t ) scratch - > get_audio ( 0 ) . capacity ( ) ) ;
2021-12-03 09:31:45 -05:00
_stretcher - > retrieve ( & bufp [ 0 ] , this_drop ) ;
2021-11-29 23:50:19 -05:00
to_drop - = this_drop ;
avail = _stretcher - > available ( ) ;
}
2022-01-06 15:48:23 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 process %2 at-end %3 avail %4 of %5 \n " , name ( ) , to_stretcher , at_end , avail , nframes ) ) ;
2021-11-11 17:10:33 -05:00
}
2021-11-29 23:50:19 -05:00
/* we've fed the stretcher enough data to have
* ( at least ) nframes of output available .
*/
from_stretcher = nframes ;
2022-01-26 23:20:04 -05:00
// cerr << "FS#1 from nframes = " << from_stretcher << endl;
2021-11-29 23:50:19 -05:00
} else {
2021-11-17 23:14:56 -05:00
2021-11-29 23:50:19 -05:00
/* finished delivering data to stretcher, but may have not yet retrieved it all */
avail = _stretcher - > available ( ) ;
from_stretcher = ( pframes_t ) std : : min ( ( pframes_t ) nframes , ( pframes_t ) avail ) ;
2022-01-26 23:20:04 -05:00
// cerr << "FS#X from avail " << avail << " nf " << nframes << " = " << from_stretcher << endl;
2021-11-11 17:10:33 -05:00
}
2021-11-17 23:14:56 -05:00
2021-11-29 23:50:19 -05:00
/* fetch the stretch */
2021-11-17 23:14:56 -05:00
2021-12-03 09:31:45 -05:00
retrieved + = _stretcher - > retrieve ( & bufp [ 0 ] , from_stretcher ) ;
2021-11-11 17:10:33 -05:00
2022-01-26 23:20:04 -05:00
if ( read_index > = last_readable_sample ) {
2022-01-10 16:01:20 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 no more data to deliver to stretcher, but retrieved %2 to put current end at %3 vs %4 / %5 pi %6 \n " ,
2022-01-26 23:20:04 -05:00
index ( ) , retrieved , transition_samples + retrieved , expected_end_sample , final_processed_sample , process_index ) ) ;
2022-01-10 16:01:20 -05:00
2021-11-29 23:50:19 -05:00
if ( transition_samples + retrieved > expected_end_sample ) {
2022-01-10 16:01:20 -05:00
/* final pull from stretched data into output buffers */
2022-03-05 13:49:55 -05:00
// cerr << "FS#2 from ees " << final_processed_sample << " - " << process_index << " & " << from_stretcher;
2022-01-31 17:55:53 -05:00
from_stretcher = std : : min ( ( samplecnt_t ) from_stretcher , final_processed_sample - process_index ) ;
2022-03-05 13:49:55 -05:00
// cerr << " => " << from_stretcher << endl;
2022-01-10 16:01:20 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 total retrieved data %2 exceeds theoretical size %3, truncate from_stretcher to %4 \n " ,
index ( ) , retrieved , expected_end_sample - transition_samples , from_stretcher ) ) ;
2021-11-11 17:10:33 -05:00
2021-11-29 23:50:19 -05:00
if ( from_stretcher = = 0 ) {
2022-01-10 16:01:20 -05:00
2022-01-26 23:20:04 -05:00
if ( process_index < final_processed_sample ) {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached (EX) end, entering playout mode to cover %2 .. %3 \n " , index ( ) , process_index , final_processed_sample ) ) ;
2022-02-18 14:49:15 -05:00
_playout = true ;
2022-01-10 16:01:20 -05:00
} else {
_state = Stopped ;
_loop_cnt + + ;
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached (EX) end, now stopped, retrieved %2, avail %3 pi %4 vs fs %5 LC now %6 \n " , index ( ) , retrieved , avail , process_index , final_processed_sample , _loop_cnt ) ) ;
2022-01-10 16:01:20 -05:00
}
2021-11-29 23:50:19 -05:00
break ;
}
2022-01-10 16:01:20 -05:00
2021-11-29 23:50:19 -05:00
}
}
2021-11-11 17:10:33 -05:00
2021-11-29 23:50:19 -05:00
} else {
/* no stretch */
2022-01-26 23:20:04 -05:00
from_stretcher = ( pframes_t ) std : : min ( ( samplecnt_t ) nframes , ( last_readable_sample - read_index ) ) ;
// cerr << "FS#3 from lrs " << last_readable_sample << " - " << read_index << " = " << from_stretcher << endl;
2021-11-11 17:10:33 -05:00
}
2022-01-26 23:20:04 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 ready with %2 ri %3 ls %4, will write %5 \n " , name ( ) , avail , read_index , last_readable_sample , from_stretcher ) ) ;
2021-11-11 17:10:33 -05:00
/* deliver to buffers */
2022-02-14 20:13:06 -05:00
if ( in_process_context ) { /* constexpr, will be handled at compile time */
2021-08-06 23:26:50 -04:00
2022-02-07 17:44:08 -05:00
for ( uint32_t chn = 0 ; chn < bufs . count ( ) . n_audio ( ) ; + + chn ) {
2021-08-11 16:18:34 -04:00
2022-02-07 17:44:08 -05:00
uint32_t channel = chn % data . size ( ) ;
AudioBuffer & buf ( bufs . get_audio ( chn ) ) ;
Sample * src = do_stretch ? bufp [ channel ] : ( data [ channel ] + read_index ) ;
2022-01-10 20:33:05 -05:00
2022-02-07 17:44:08 -05:00
gain_t gain = _velocity_gain * _gain ; //incorporate the gain from velocity_effect
if ( gain ! = 1.0f ) {
buf . accumulate_with_gain_from ( src , from_stretcher , gain , dest_offset ) ;
} else {
buf . accumulate_from ( src , from_stretcher , dest_offset ) ;
}
2021-08-10 22:35:39 -04:00
}
}
2021-08-06 23:26:50 -04:00
2021-11-29 23:50:19 -05:00
process_index + = from_stretcher ;
2022-01-10 16:01:20 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 pi grew by %2 to %3 \n " , index ( ) , from_stretcher , process_index ) ) ;
2021-08-06 23:26:50 -04:00
2021-11-29 23:50:19 -05:00
/* Move read_index, in the case that we are not using a
* stretcher
*/
2021-11-17 23:14:56 -05:00
2021-12-15 12:21:28 -05:00
if ( ! do_stretch ) {
2021-11-29 23:50:19 -05:00
read_index + = from_stretcher ;
}
2021-08-07 17:08:26 -04:00
2021-11-29 23:50:19 -05:00
nframes - = from_stretcher ;
avail = _stretcher - > available ( ) ;
dest_offset + = from_stretcher ;
2021-10-07 00:40:37 -04:00
2022-01-26 23:20:04 -05:00
if ( read_index > = last_readable_sample & & ( ! do_stretch | | avail < = 0 ) ) {
2022-01-07 14:46:04 -05:00
2022-01-26 23:20:04 -05:00
if ( process_index < final_processed_sample ) {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached end, entering playout mode to cover %2 .. %3 \n " , index ( ) , process_index , final_processed_sample ) ) ;
2022-02-18 14:49:15 -05:00
_playout = true ;
2022-01-07 14:46:04 -05:00
} else {
_state = Stopped ;
_loop_cnt + + ;
2022-02-18 14:49:15 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached end, now stopped, retrieved %2, avail %3 LC now %4 \n " , index ( ) , retrieved , avail , _loop_cnt ) ) ;
2022-01-07 14:46:04 -05:00
}
2021-11-29 23:50:19 -05:00
break ;
}
}
2021-11-17 23:14:56 -05:00
2022-01-07 14:46:04 -05:00
pframes_t covered_frames = orig_nframes - nframes ;
2022-02-18 14:49:15 -05:00
if ( _playout ) {
2022-01-07 14:46:04 -05:00
2022-01-10 16:01:20 -05:00
if ( nframes ! = orig_nframes ) {
/* we've already taken dest_offset into account, it plays no
role in a " playout " during the same : : run ( ) call
*/
dest_offset = 0 ;
}
2022-01-07 14:46:04 -05:00
const pframes_t remaining_frames_for_run = orig_nframes - covered_frames ;
2022-01-26 23:20:04 -05:00
const pframes_t remaining_frames_till_final = final_processed_sample - process_index ;
2022-01-07 14:46:04 -05:00
const pframes_t to_fill = std : : min ( remaining_frames_till_final , remaining_frames_for_run ) ;
2022-01-10 16:01:20 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 playout mode, remaining in run %2 till final %3 @ %5 ts %7 vs pi @ %6 to fill %4 \n " ,
2022-01-26 23:20:04 -05:00
index ( ) , remaining_frames_for_run , remaining_frames_till_final , to_fill , final_processed_sample , process_index , transition_samples ) ) ;
2022-01-07 14:46:04 -05:00
2022-01-10 16:01:20 -05:00
if ( remaining_frames_till_final ! = 0 ) {
2022-01-07 14:46:04 -05:00
2022-01-10 16:01:20 -05:00
process_index + = to_fill ;
covered_frames + = to_fill ;
2022-01-07 14:46:04 -05:00
2022-01-26 23:20:04 -05:00
if ( process_index < final_processed_sample ) {
2022-01-10 16:01:20 -05:00
/* more playout to be done */
return covered_frames ;
}
2022-01-07 14:46:04 -05:00
}
2022-01-10 16:01:20 -05:00
_state = Stopped ;
2022-01-07 14:46:04 -05:00
_loop_cnt + + ;
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 playout finished, LC now %4 \n " , index ( ) , _loop_cnt ) ) ;
2022-01-07 14:46:04 -05:00
}
2021-12-14 23:55:52 -05:00
if ( _state = = Stopped | | _state = = Stopping ) {
2022-01-10 16:01:20 -05:00
/* note: neither argument is used in the audio case */
2022-01-06 18:50:55 -05:00
when_stopped_during_run ( bufs , dest_offset ) ;
2021-08-11 16:18:34 -04:00
}
2022-01-07 14:46:04 -05:00
return covered_frames ;
2021-07-17 15:21:45 -04:00
}
2021-08-08 21:08:16 -04:00
2021-11-04 16:00:16 -04:00
void
AudioTrigger : : reload ( BufferSet & , void * )
{
}
2021-10-20 18:19:09 -04:00
/*--------------------*/
2021-12-21 15:37:47 -05:00
MIDITrigger : : MIDITrigger ( uint32_t n , TriggerBox & b )
2021-10-20 18:19:09 -04:00
: Trigger ( n , b )
2022-01-12 16:12:15 -05:00
, data_length ( Temporal : : Beats ( ) )
, last_event_beats ( Temporal : : Beats ( ) )
2022-01-13 14:09:23 -05:00
, _start_offset ( 0 , 0 , 0 )
2021-10-20 18:19:09 -04:00
, _legato_offset ( 0 , 0 , 0 )
{
2022-02-03 14:37:49 -05:00
_channel_map . assign ( 16 , - 1 ) ;
2021-10-20 18:19:09 -04:00
}
MIDITrigger : : ~ MIDITrigger ( )
{
}
2022-02-24 15:38:58 -05:00
void
MIDITrigger : : set_used_channels ( Evoral : : SMF : : UsedChannels used )
{
2022-03-20 16:19:00 -04:00
if ( ui_state . used_channels ! = used ) {
2022-02-24 15:38:58 -05:00
2022-03-20 16:19:00 -04:00
/* increment ui_state generation so vals will get loaded when the trigger stops */
unsigned int g = ui_state . generation . load ( ) ;
while ( ! ui_state . generation . compare_exchange_strong ( g , g + 1 ) ) ;
ui_state . used_channels = used ;
2022-02-24 15:38:58 -05:00
send_property_change ( ARDOUR : : Properties : : used_channels ) ;
_box . session ( ) . set_dirty ( ) ;
}
}
2022-02-03 14:37:49 -05:00
void
MIDITrigger : : set_channel_map ( int channel , int target )
{
if ( channel < 0 | | channel > = 16 ) {
return ;
}
2022-02-03 15:07:04 -05:00
2022-02-03 14:37:49 -05:00
if ( target < 0 | | target > = 16 ) {
return ;
}
2022-02-03 15:07:04 -05:00
if ( _channel_map [ channel ] ! = target ) {
_channel_map [ channel ] = target ;
2022-02-11 12:16:04 -05:00
send_property_change ( Properties : : channel_map ) ;
2022-02-03 15:07:04 -05:00
}
2022-02-03 14:37:49 -05:00
}
void
MIDITrigger : : unset_channel_map ( int channel )
{
if ( channel < 0 | | channel > = 16 ) {
return ;
}
2022-02-03 15:07:04 -05:00
if ( _channel_map [ channel ] > = 0 ) {
_channel_map [ channel ] = - 1 ;
2022-02-11 12:16:04 -05:00
send_property_change ( Properties : : channel_map ) ;
2022-02-03 15:07:04 -05:00
}
2022-02-03 14:37:49 -05:00
}
int
MIDITrigger : : channel_map ( int channel )
{
if ( channel < 0 | | channel > = 16 ) {
return - 1 ;
}
return _channel_map [ channel ] ;
}
2022-02-02 14:45:57 -05:00
void
MIDITrigger : : set_patch_change ( Evoral : : PatchChange < MidiBuffer : : TimeType > const & pc )
{
2022-03-20 16:20:16 -04:00
/* this must recreate the behavior of TRIGGER_SET, but it requires special handling because its an array */
/* specifically, we need to make sure and set the ui_state as well as the internal property, so the triggerbox won't overwrite these changes when it loads the trigger state */
2022-02-02 14:45:57 -05:00
assert ( pc . is_set ( ) ) ;
2022-03-20 16:20:16 -04:00
ui_state . patch_change [ pc . channel ( ) ] = pc ;
/* increment ui_state generation so vals will get loaded when the trigger stops */
unsigned int g = ui_state . generation . load ( ) ;
while ( ! ui_state . generation . compare_exchange_strong ( g , g + 1 ) ) ;
2022-02-11 12:16:04 -05:00
send_property_change ( Properties : : patch_change ) ;
2022-02-02 14:45:57 -05:00
}
void
MIDITrigger : : unset_all_patch_changes ( )
{
2022-03-20 16:20:16 -04:00
/* this must recreate the behavior of TRIGGER_SET, but it requires special handling because its an array */
/* specifically, we need to make sure and set the ui_state as well as the internal property, so the triggerbox won't overwrite these changes when it loads the trigger state */
2022-02-02 14:45:57 -05:00
for ( uint8_t chn = 0 ; chn < 16 ; + + chn ) {
2022-03-20 16:20:16 -04:00
if ( ui_state . patch_change [ chn ] . is_set ( ) ) {
ui_state . patch_change [ chn ] . unset ( ) ;
}
2022-02-02 14:45:57 -05:00
}
2022-03-20 16:20:16 -04:00
/* increment ui_state generation so vals will get loaded when the trigger stops */
unsigned int g = ui_state . generation . load ( ) ;
while ( ! ui_state . generation . compare_exchange_strong ( g , g + 1 ) ) ;
send_property_change ( Properties : : patch_change ) ;
2022-02-02 14:45:57 -05:00
}
void
MIDITrigger : : unset_patch_change ( uint8_t channel )
{
2022-03-20 16:20:16 -04:00
/* this must recreate the behavior of TRIGGER_SET_DIRECT, but it requires special handling because its an array */
/* specifically, we need to make sure and set the ui_state as well as the internal property, so the triggerbox won't overwrite these changes when it loads the trigger state */
2022-02-02 14:45:57 -05:00
assert ( channel < 16 ) ;
2022-03-20 16:20:16 -04:00
/* increment ui_state generation so vals will get loaded when the trigger stops */
unsigned int g = ui_state . generation . load ( ) ;
while ( ! ui_state . generation . compare_exchange_strong ( g , g + 1 ) ) ;
if ( ui_state . patch_change [ channel ] . is_set ( ) ) {
ui_state . patch_change [ channel ] . unset ( ) ;
2022-02-02 14:45:57 -05:00
}
2022-03-20 16:20:16 -04:00
send_property_change ( Properties : : patch_change ) ;
2022-02-02 14:45:57 -05:00
}
bool
MIDITrigger : : patch_change_set ( uint8_t channel ) const
{
assert ( channel < 16 ) ;
2022-03-20 16:20:16 -04:00
return ui_state . patch_change [ channel ] . is_set ( ) ;
2022-02-02 14:45:57 -05:00
}
2022-02-24 15:38:58 -05:00
Evoral : : PatchChange < MidiBuffer : : TimeType > const
2022-02-02 14:45:57 -05:00
MIDITrigger : : patch_change ( uint8_t channel ) const
{
2022-02-24 15:38:58 -05:00
Evoral : : PatchChange < MidiBuffer : : TimeType > ret ;
2022-02-02 14:45:57 -05:00
assert ( channel < 16 ) ;
2022-03-20 16:20:16 -04:00
if ( ui_state . patch_change [ channel ] . is_set ( ) ) {
ret = ui_state . patch_change [ channel ] ;
2022-02-26 09:41:18 -05:00
}
2022-02-24 15:38:58 -05:00
return ret ;
2022-02-02 14:45:57 -05:00
}
2021-12-22 15:09:48 -05:00
bool
MIDITrigger : : probably_oneshot ( ) const
{
/* XXX fix for short chord stabs */
return false ;
}
2021-12-15 12:21:28 -05:00
void
2022-02-08 19:05:15 -05:00
MIDITrigger : : start_and_roll_to ( samplepos_t start_pos , samplepos_t end_position )
2022-02-07 17:44:08 -05:00
{
2022-02-15 18:24:01 -05:00
Trigger : : start_and_roll_to ( start_pos , end_position , * this , & MIDITrigger : : midi_run < false > ) ;
2022-02-07 17:44:08 -05:00
}
2022-02-08 18:59:36 -05:00
timepos_t
2022-03-19 17:24:41 -04:00
MIDITrigger : : compute_end ( Temporal : : TempoMap : : SharedPtr const & tmap , Temporal : : BBT_Time const & transition_bbt , samplepos_t , Temporal : : Beats & effective_length )
2021-12-15 12:21:28 -05:00
{
2022-01-11 13:32:35 -05:00
Temporal : : Beats end_by_follow_length = tmap - > quarters_at ( tmap - > bbt_walk ( transition_bbt , _follow_length ) ) ;
Temporal : : Beats end_by_data_length = transition_beats + data_length ;
2022-01-10 16:01:20 -05:00
2022-01-11 13:32:35 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 ends: FL %2 DL %3 tbbt %4 fl %5 \n " , index ( ) , end_by_follow_length , end_by_data_length , transition_bbt , _follow_length ) ) ;
2022-01-10 16:01:20 -05:00
2022-01-11 13:32:35 -05:00
Temporal : : BBT_Offset q ( _quantization ) ;
2022-01-10 16:01:20 -05:00
2022-01-11 13:32:35 -05:00
if ( launch_style ( ) ! = Repeat | | ( q = = Temporal : : BBT_Offset ( ) ) ) {
2022-01-10 16:01:20 -05:00
2022-01-20 16:03:40 -05:00
if ( internal_use_follow_length ( ) ) {
2022-01-11 13:32:35 -05:00
final_beat = end_by_follow_length ;
2022-03-19 17:24:41 -04:00
effective_length = tmap - > bbtwalk_to_quarters ( transition_bbt , _follow_length ) ;
2022-01-11 13:32:35 -05:00
} else {
final_beat = end_by_data_length ;
2022-03-19 17:24:41 -04:00
effective_length = tmap - > bbtwalk_to_quarters ( transition_bbt , Temporal : : BBT_Offset ( 0 , data_length . get_beats ( ) , data_length . get_ticks ( ) ) ) ;
2022-01-10 16:01:20 -05:00
}
2022-01-11 13:32:35 -05:00
} else {
2022-01-10 16:01:20 -05:00
/* XXX MUST HANDLE BAR-LEVEL QUANTIZATION */
timecnt_t len ( Temporal : : Beats ( q . beats , q . ticks ) , timepos_t ( Temporal : : Beats ( ) ) ) ;
2022-01-11 13:32:35 -05:00
final_beat = len . beats ( ) ;
2022-01-10 16:01:20 -05:00
}
2022-02-08 18:59:36 -05:00
2022-02-14 11:52:43 -05:00
timepos_t e ( final_beat ) ;
final_processed_sample = e . samples ( ) - transition_samples ;
return e ;
2021-12-15 12:21:28 -05:00
}
2021-12-12 13:20:58 -05:00
SegmentDescriptor
MIDITrigger : : get_segment_descriptor ( ) const
{
SegmentDescriptor sd ;
boost : : shared_ptr < MidiRegion > mr = boost : : dynamic_pointer_cast < MidiRegion > ( _region ) ;
assert ( mr ) ;
sd . set_extent ( Temporal : : Beats ( ) , mr - > length ( ) . beats ( ) ) ;
/* we don't really have tempo information for MIDI yet */
sd . set_tempo ( Temporal : : Tempo ( 120 , 4 ) ) ;
return sd ;
}
2021-10-20 18:19:09 -04:00
void
2022-02-02 17:04:22 -05:00
MIDITrigger : : _startup ( BufferSet & bufs , pframes_t dest_offset , Temporal : : BBT_Offset const & start_quantization )
2021-10-20 18:19:09 -04:00
{
2022-02-02 17:04:22 -05:00
Trigger : : _startup ( bufs , dest_offset , start_quantization ) ;
2022-02-10 21:59:42 -05:00
MidiBuffer * mb = 0 ;
if ( bufs . count ( ) . n_midi ( ) ! = 0 ) {
mb = & bufs . get_midi ( 0 ) ;
}
2022-02-02 17:04:22 -05:00
/* Possibly inject patch changes, if set */
for ( int chn = 0 ; chn < 16 ; + + chn ) {
2022-02-28 10:50:46 -05:00
if ( _used_channels . test ( chn ) & & allow_patch_changes ( ) & & _patch_change [ chn ] . is_set ( ) ) {
2022-02-02 17:04:22 -05:00
_patch_change [ chn ] . set_time ( dest_offset ) ;
2022-02-26 09:41:18 -05:00
DEBUG_TRACE ( DEBUG : : MidiTriggers , string_compose ( " Injecting patch change c:%1 b:%2 p:%3 \n " , ( uint32_t ) _patch_change [ chn ] . channel ( ) , ( uint32_t ) _patch_change [ chn ] . bank ( ) , ( uint32_t ) _patch_change [ chn ] . program ( ) ) ) ;
2022-02-02 17:04:22 -05:00
for ( int msg = 0 ; msg < _patch_change [ chn ] . messages ( ) ; + + msg ) {
2022-02-10 21:59:42 -05:00
if ( mb ) {
mb - > insert_event ( _patch_change [ chn ] . message ( msg ) ) ;
2022-02-26 09:41:18 -05:00
_box . tracker - > track ( _patch_change [ chn ] . message ( msg ) . buffer ( ) ) ;
2022-02-10 21:59:42 -05:00
}
2022-02-02 17:04:22 -05:00
}
}
}
2021-10-20 18:19:09 -04:00
}
void
MIDITrigger : : jump_start ( )
{
Trigger : : jump_start ( ) ;
retrigger ( ) ;
}
void
2022-01-06 18:50:55 -05:00
MIDITrigger : : shutdown ( BufferSet & bufs , pframes_t dest_offset )
2021-10-20 18:19:09 -04:00
{
2022-01-06 18:50:55 -05:00
Trigger : : shutdown ( bufs , dest_offset ) ;
2022-02-10 21:59:42 -05:00
if ( bufs . count ( ) . n_midi ( ) ) {
MidiBuffer & mb ( bufs . get_midi ( 0 ) ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 shutdown, resolve notes @ %2 \n " , index ( ) , dest_offset ) ) ;
_box . tracker - > resolve_notes ( mb , dest_offset ) ;
}
_box . tracker - > reset ( ) ;
2022-01-06 18:50:55 -05:00
}
void
MIDITrigger : : jump_stop ( BufferSet & bufs , pframes_t dest_offset )
{
Trigger : : jump_stop ( bufs , dest_offset ) ;
MidiBuffer & mb ( bufs . get_midi ( 0 ) ) ;
2022-02-08 18:59:36 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 jump stop, resolve notes @ %2 \n " , index ( ) , dest_offset ) ) ;
2022-02-01 20:03:51 -05:00
_box . tracker - > resolve_notes ( mb , dest_offset ) ;
2022-01-06 18:50:55 -05:00
2021-10-20 18:19:09 -04:00
retrigger ( ) ;
}
XMLNode &
2022-04-06 23:56:32 -04:00
MIDITrigger : : get_state ( ) const
2021-10-20 18:19:09 -04:00
{
XMLNode & node ( Trigger : : get_state ( ) ) ;
node . set_property ( X_ ( " start " ) , start_offset ( ) ) ;
2022-02-24 15:38:58 -05:00
std : : string uchan = string_compose ( " %1 " , _used_channels . to_ulong ( ) ) ;
node . set_property ( X_ ( " used-channels " ) , uchan ) ;
2022-02-03 11:24:30 -05:00
XMLNode * patches_node = 0 ;
for ( int chn = 0 ; chn < 16 ; + + chn ) {
if ( _patch_change [ chn ] . is_set ( ) ) {
if ( ! patches_node ) {
patches_node = new XMLNode ( X_ ( " PatchChanges " ) ) ;
}
XMLNode * patch_node = new XMLNode ( X_ ( " PatchChange " ) ) ;
patch_node - > set_property ( X_ ( " channel " ) , _patch_change [ chn ] . channel ( ) ) ;
patch_node - > set_property ( X_ ( " bank " ) , _patch_change [ chn ] . bank ( ) ) ;
patch_node - > set_property ( X_ ( " program " ) , _patch_change [ chn ] . program ( ) ) ;
patches_node - > add_child_nocopy ( * patch_node ) ;
}
}
if ( patches_node ) {
node . add_child_nocopy ( * patches_node ) ;
}
2022-02-03 15:07:04 -05:00
std : : string cmstr ;
for ( int chn = 0 ; chn < 16 ; + + chn ) {
char buf [ 4 ] ;
if ( chn > 0 ) {
cmstr + = ' , ' ;
}
snprintf ( buf , sizeof ( buf ) , " %d " , _channel_map [ chn ] ) ;
cmstr + = buf ;
}
node . set_property ( X_ ( " channel-map " ) , cmstr ) ;
2021-10-20 18:19:09 -04:00
return node ;
}
int
MIDITrigger : : set_state ( const XMLNode & node , int version )
{
timepos_t t ;
2022-02-08 20:30:10 -05:00
if ( Trigger : : set_state ( node , version ) ) {
2021-10-20 18:19:09 -04:00
return - 1 ;
}
2022-02-24 15:38:58 -05:00
std : : string uchan ;
if ( node . get_property ( X_ ( " used-channels " ) , uchan ) ) {
} else {
unsigned long ul ;
std : : stringstream ss ( uchan ) ;
ss > > ul ;
if ( ! ss ) {
return - 1 ;
}
set_used_channels ( Evoral : : SMF : : UsedChannels ( ul ) ) ;
}
2021-10-20 18:19:09 -04:00
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 ( ) ) ;
2022-02-03 11:24:30 -05:00
XMLNode * patches_node = node . child ( X_ ( " PatchChanges " ) ) ;
if ( patches_node ) {
XMLNodeList const & children = patches_node - > children ( ) ;
for ( XMLNodeList : : const_iterator i = children . begin ( ) ; i ! = children . end ( ) ; + + i ) {
if ( ( * i ) - > name ( ) = = X_ ( " PatchChange " ) ) {
int c , p , b ;
if ( ( * i ) - > get_property ( X_ ( " channel " ) , c ) & &
( * i ) - > get_property ( X_ ( " program " ) , p ) & &
2022-02-08 20:30:10 -05:00
( * i ) - > get_property ( X_ ( " bank " ) , b ) ) {
2022-02-03 11:24:30 -05:00
_patch_change [ c ] = Evoral : : PatchChange < MidiBuffer : : TimeType > ( 0 , c , p , b ) ;
}
}
}
2022-02-02 14:45:57 -05:00
}
2021-10-20 18:19:09 -04:00
2022-02-03 15:07:04 -05:00
std : : string cmstr ;
if ( node . get_property ( X_ ( " channel-map " ) , cmstr ) ) {
std : : stringstream ss ( cmstr ) ;
char comma ;
for ( int chn = 0 ; chn < 16 ; + + chn ) {
ss > > _channel_map [ chn ] ;
if ( ! ss ) {
break ;
}
ss > > comma ;
if ( ! ss ) {
break ;
}
}
}
2022-02-24 15:38:58 -05:00
/* we've changed our internal values; we need to update our queued UIState or they will be lost when UIState is applied */
copy_to_ui_state ( ) ;
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
}
void
MIDITrigger : : set_length ( timecnt_t const & newlen )
{
2021-10-24 11:27:14 -04:00
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 ) ;
}
2022-02-24 15:37:19 -05:00
void
MIDITrigger : : estimate_midi_patches ( )
{
2022-05-08 20:20:45 -04:00
/* first, initialize all our slot's patches to GM defaults, to make playback deterministic */
2022-02-24 15:37:19 -05:00
for ( uint8_t chan = 0 ; chan < 16 ; + + chan ) {
_patch_change [ chan ] . set_channel ( chan ) ;
_patch_change [ chan ] . set_bank ( chan = = 9 ? 120 : 0 ) ;
_patch_change [ chan ] . set_program ( 0 ) ;
}
boost : : shared_ptr < SMFSource > smfs = boost : : dynamic_pointer_cast < SMFSource > ( _region - > source ( 0 ) ) ;
if ( smfs ) {
/* second, apply any patches that the Auditioner has in its memory
* . . . this handles the case where the user chose patches for a file that itself lacked patch - settings
* ( it ' s possible that the user didn ' t audition the actual file they dragged in , but this is still the best starting - point we have )
* */
boost : : shared_ptr < ARDOUR : : Auditioner > aud = _box . session ( ) . the_auditioner ( ) ;
if ( aud ) {
for ( uint8_t chan = 0 ; chan < 16 ; + + chan ) {
if ( aud - > patch_change ( chan ) . is_set ( ) ) {
_patch_change [ chan ] = aud - > patch_change ( chan ) ;
}
}
}
/* thirdly, apply the patches from the file itself (if it has any) */
boost : : shared_ptr < MidiModel > model = smfs - > model ( ) ;
for ( MidiModel : : PatchChanges : : const_iterator i = model - > patch_changes ( ) . begin ( ) ; i ! = model - > patch_changes ( ) . end ( ) ; + + i ) {
if ( ( * i ) - > is_set ( ) ) {
int chan = ( * i ) - > channel ( ) ; /* behavior is undefined for SMF's with multiple patch changes. I'm not sure that we care */
_patch_change [ chan ] . set_channel ( ( * i ) - > channel ( ) ) ;
_patch_change [ chan ] . set_bank ( ( * i ) - > bank ( ) ) ;
_patch_change [ chan ] . set_program ( ( * i ) - > program ( ) ) ;
}
}
/* finally, store the used_channels so the UI can show patches only for those chans actually used */
2022-02-28 10:50:46 -05:00
DEBUG_TRACE ( DEBUG : : MidiTriggers , string_compose ( " %1 estimate_midi_patches(), using channels %2 \n " , name ( ) , smfs - > used_channels ( ) . to_string ( ) . c_str ( ) ) ) ;
2022-03-20 16:19:00 -04:00
_used_channels = smfs - > used_channels ( ) ;
2022-02-24 15:37:19 -05:00
}
2022-03-20 16:19:00 -04:00
//we've changed some of our internal values; the calling code must call copy_to_ui_state ... ::set_region_in_worker_thread does it
2022-02-24 15:37:19 -05:00
}
2022-02-24 15:38:58 -05:00
2021-10-20 18:19:09 -04:00
int
2021-12-22 09:42:52 -05:00
MIDITrigger : : set_region_in_worker_thread ( boost : : shared_ptr < Region > r )
2021-10-20 18:19:09 -04:00
{
boost : : shared_ptr < MidiRegion > mr = boost : : dynamic_pointer_cast < MidiRegion > ( r ) ;
if ( ! mr ) {
return - 1 ;
}
set_region_internal ( r ) ;
2021-11-19 16:50:50 -05:00
set_name ( mr - > name ( ) ) ;
data_length = mr - > length ( ) . beats ( ) ;
2022-04-26 10:41:53 -04:00
_follow_length = Temporal : : BBT_Offset ( 0 , data_length . get_beats ( ) , 0 ) ;
2021-10-20 18:19:09 -04:00
set_length ( mr - > length ( ) ) ;
2021-11-19 16:50:50 -05:00
model = mr - > model ( ) ;
2021-10-20 18:19:09 -04:00
2022-02-24 15:37:19 -05:00
estimate_midi_patches ( ) ;
/* we've changed some of our internal values; we need to update our queued UIState or they will be lost when UIState is applied */
copy_to_ui_state ( ) ;
2021-11-19 16:50:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 loaded midi region, span is %2 \n " , name ( ) , data_length ) ) ;
2021-11-04 16:00:16 -04:00
2022-02-11 12:16:04 -05:00
send_property_change ( ARDOUR : : Properties : : name ) ;
2021-10-20 18:19:09 -04:00
return 0 ;
}
void
MIDITrigger : : retrigger ( )
{
2022-02-14 11:52:43 -05:00
Trigger : : retrigger ( ) ;
2022-01-25 20:06:42 -05:00
update_properties ( ) ;
2021-10-20 18:19:09 -04:00
/* XXX need to deal with bar offsets */
2021-10-24 11:27:14 -04:00
// const Temporal::BBT_Offset o = _start_offset + _legato_offset;
2021-11-19 16:50:50 -05:00
iter = model - > begin ( ) ;
2021-10-20 18:19:09 -04:00
_legato_offset = Temporal : : BBT_Offset ( ) ;
2021-11-19 16:50:50 -05:00
last_event_beats = Temporal : : Beats ( ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 retriggered to %2, ts = %3 \n " , _index , iter - > time ( ) , transition_beats ) ) ;
}
void
MIDITrigger : : reload ( BufferSet & , void * )
{
2021-10-20 18:19:09 -04:00
}
2022-02-14 20:13:06 -05:00
template < bool in_process_context >
2021-11-29 23:50:19 -05:00
pframes_t
2022-02-07 17:44:08 -05:00
MIDITrigger : : midi_run ( BufferSet & bufs , samplepos_t start_sample , samplepos_t end_sample ,
Temporal : : Beats const & start_beats , Temporal : : Beats const & end_beats ,
pframes_t nframes , pframes_t dest_offset , double bpm )
2021-10-20 18:19:09 -04:00
{
2022-02-14 20:13:06 -05:00
MidiBuffer * mb ( in_process_context ? & bufs . get_midi ( 0 ) : 0 ) ;
2021-11-19 16:50:50 -05:00
typedef Evoral : : Event < MidiModel : : TimeType > MidiEvent ;
const timepos_t region_start_time = _region - > start ( ) ;
const Temporal : : Beats region_start = region_start_time . beats ( ) ;
Temporal : : TempoMap : : SharedPtr tmap ( Temporal : : TempoMap : : use ( ) ) ;
2021-12-04 13:49:09 -05:00
samplepos_t last_event_samples = max_samplepos ;
2021-10-24 11:27:14 -04:00
2021-12-02 13:27:18 -05:00
/* see if we're going to start or stop or retrigger in this run() call */
2022-02-14 14:56:18 -05:00
pframes_t ignore_computed_dest_offset = 0 ;
maybe_compute_next_transition ( start_sample , start_beats , end_beats , nframes , ignore_computed_dest_offset ) ;
2022-01-06 15:48:23 -05:00
const pframes_t orig_nframes = nframes ;
2021-12-27 17:08:20 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 after checking for transition, state = %2 \n " , name ( ) , enum_2_string ( _state ) ) ) ;
2021-12-02 13:27:18 -05:00
switch ( _state ) {
case Stopped :
case WaitingForRetrigger :
case WaitingToStart :
return nframes ;
case Running :
case WaitingToStop :
2022-02-20 11:20:47 -05:00
case WaitingToSwitch :
2021-12-02 13:27:18 -05:00
case Stopping :
break ;
}
2022-02-18 14:53:26 -05:00
Temporal : : Beats last_event_timeline_beats = final_beat ; /* will indicate "done" if there is nothing to do */
2022-01-12 16:12:15 -05:00
2022-02-18 14:49:15 -05:00
while ( iter ! = model - > end ( ) & & ! _playout ) {
2021-10-24 11:27:14 -04:00
2022-01-12 16:12:15 -05:00
MidiEvent const & event ( * iter ) ;
2021-11-19 16:50:50 -05:00
/* Event times are in beats, relative to start of source
* file . We need to convert to region - relative time , and then
* a session timeline time , which is defined by the time at
* which we last transitioned ( in this case , to being active )
*/
2021-10-26 19:34:06 -04:00
2022-01-12 16:12:15 -05:00
const Temporal : : Beats maybe_last_event_timeline_beats = transition_beats + ( event . time ( ) - region_start ) ;
2022-02-08 18:59:36 -05:00
if ( maybe_last_event_timeline_beats > final_beat ) {
2022-01-12 16:12:15 -05:00
/* do this to "fake" having reached the end */
2022-02-08 18:59:36 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 tlrr %2 >= fb %3, so at end with %4 \n " , index ( ) , maybe_last_event_timeline_beats , final_beat , event ) ) ;
2022-01-12 16:12:15 -05:00
iter = model - > end ( ) ;
break ;
}
2021-10-26 19:34:06 -04:00
2021-11-19 16:50:50 -05:00
/* Now get samples */
2021-10-25 14:53:49 -04:00
2022-01-12 16:12:15 -05:00
const samplepos_t timeline_samples = tmap - > sample_at ( maybe_last_event_timeline_beats ) ;
2021-10-25 14:53:49 -04:00
2021-12-02 13:27:18 -05:00
if ( timeline_samples > = end_sample ) {
2021-11-19 16:50:50 -05:00
break ;
}
2021-10-29 18:22:55 -04:00
2022-02-14 20:13:06 -05:00
if ( in_process_context ) { /* compile-time const expr */
2022-02-07 17:44:08 -05:00
/* Now we have to convert to a position within the buffer we
* are writing to .
*
2022-02-08 18:59:36 -05:00
* ( timeline_samples - start_sample ) gives us the
* sample offset from the start of our run ( ) call . But
* since we may be executing after another trigger in
* the same process ( ) cycle , we must take dest_offset
* into account to get an actual buffer position .
2022-02-07 17:44:08 -05:00
*/
2021-10-29 18:22:55 -04:00
2022-02-08 18:59:36 -05:00
samplepos_t buffer_samples = ( timeline_samples - start_sample ) + dest_offset ;
2021-10-29 18:22:55 -04:00
2022-02-07 17:44:08 -05:00
Evoral : : Event < MidiBuffer : : TimeType > ev ( Evoral : : MIDI_EVENT , buffer_samples , event . size ( ) , const_cast < uint8_t * > ( event . buffer ( ) ) , false ) ;
2022-02-02 11:58:35 -05:00
2022-02-07 17:44:08 -05:00
if ( _gain ! = 1.0f & & ev . is_note ( ) ) {
ev . scale_velocity ( _gain ) ;
}
2022-02-02 11:58:35 -05:00
2022-02-26 09:41:18 -05:00
int chn = ev . channel ( ) ;
2022-02-08 23:08:15 -05:00
if ( _channel_map [ ev . channel ( ) ] > 0 ) {
2022-02-26 09:41:18 -05:00
ev . set_channel ( _channel_map [ chn ] ) ;
2022-02-08 23:08:15 -05:00
}
2022-02-08 23:07:07 -05:00
if ( ev . is_pgm_change ( ) | | ( ev . is_cc ( ) & & ( ( ev . cc_number ( ) = = MIDI_CTL_LSB_BANK ) | | ( ev . cc_number ( ) = = MIDI_CTL_MSB_BANK ) ) ) ) {
2022-02-28 10:50:46 -05:00
if ( ! allow_patch_changes ( ) ) {
2022-02-26 09:41:18 -05:00
/* do not send ANY patch or bank messages, just skip them */
DEBUG_TRACE ( DEBUG : : MidiTriggers , string_compose ( " Ignoring patch change on chn:%1 \n " , ( uint32_t ) _patch_change [ chn ] . channel ( ) ) ) ;
2022-02-08 23:07:07 -05:00
+ + iter ;
continue ;
2022-02-26 09:41:18 -05:00
} else if ( _patch_change [ chn ] . is_set ( ) ) {
/* from this context we don't know if a pgm message in the midi buffer is from the file or from triggerbox */
/* so when a bank or pgm message is recognized, just replace it with the desired patch */
DEBUG_TRACE ( DEBUG : : MidiTriggers , string_compose ( " Replacing patch change c:%1 b:%2 p:%3 \n " , ( uint32_t ) _patch_change [ chn ] . channel ( ) , ( uint32_t ) _patch_change [ chn ] . bank ( ) , ( uint32_t ) _patch_change [ chn ] . program ( ) ) ) ;
if ( ev . is_cc ( ) & & ( ev . cc_number ( ) = = MIDI_CTL_MSB_BANK ) ) {
ev . set_cc_value ( _patch_change [ chn ] . bank_msb ( ) ) ;
} else if ( ev . is_cc ( ) & & ( ev . cc_number ( ) = = MIDI_CTL_LSB_BANK ) ) {
ev . set_cc_value ( _patch_change [ chn ] . bank_lsb ( ) ) ;
} else if ( ev . is_pgm_change ( ) ) {
ev . set_pgm_number ( _patch_change [ chn ] . program ( ) ) ;
}
2022-02-08 23:07:07 -05:00
}
2022-02-02 18:05:06 -05:00
}
2022-02-07 17:44:08 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " given et %1 TS %7 rs %8 ts %2 bs %3 ss %4 do %5, inserting %6 \n " , maybe_last_event_timeline_beats , timeline_samples , buffer_samples , start_sample , dest_offset , ev , transition_beats , region_start ) ) ;
mb - > insert_event ( ev ) ;
}
2022-01-12 16:12:15 -05:00
2022-02-01 20:03:51 -05:00
_box . tracker - > track ( event . buffer ( ) ) ;
2022-01-12 16:12:15 -05:00
last_event_beats = event . time ( ) ;
last_event_timeline_beats = maybe_last_event_timeline_beats ;
last_event_samples = timeline_samples ;
2021-10-26 19:34:06 -04:00
2021-11-19 16:50:50 -05:00
+ + iter ;
2021-10-25 14:53:49 -04:00
}
2021-10-24 11:27:14 -04:00
2021-11-30 09:57:34 -05:00
2022-02-14 20:13:06 -05:00
if ( in_process_context & & _state = = Stopping ) { /* first clause is a compile-time constexpr */
2022-02-08 18:59:36 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 was stopping, now stopped, resolving notes @ %2 \n " , index ( ) , nframes - 1 ) ) ;
2022-02-07 17:44:08 -05:00
_box . tracker - > resolve_notes ( * mb , nframes - 1 ) ;
2021-10-24 11:27:14 -04:00
}
2021-12-02 13:27:18 -05:00
if ( iter = = model - > end ( ) ) {
2021-11-30 09:57:34 -05:00
2021-12-02 13:27:18 -05:00
/* We reached the end */
2021-11-30 09:57:34 -05:00
2022-01-12 16:12:15 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached end, leb %2 les %3 fb %4 dl %5 \n " , index ( ) , last_event_timeline_beats , last_event_samples , final_beat , data_length ) ) ;
2021-11-30 09:57:34 -05:00
2022-02-18 20:01:52 -05:00
/* "final_beat" is an inclusive end of the trigger, not
* exclusive , so we must use < = here . That is , any last event
2022-05-08 20:20:45 -04:00
* ( remember , iter = = model - > end ( ) here , so we have already read
2022-02-18 20:01:52 -05:00
* through the entire MIDI model ) that is up to AND INCLUDING
* final_beat counts as " haven't reached the end " .
*/
if ( last_event_timeline_beats < = final_beat ) {
2022-01-11 13:32:35 -05:00
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 entering playout because ... leb %2 < fb %3 \n " , index ( ) , last_event_timeline_beats , final_beat ) ) ;
2022-01-11 13:32:35 -05:00
2022-02-18 14:49:15 -05:00
_playout = true ;
2022-01-11 13:32:35 -05:00
2022-02-18 14:49:15 -05:00
if ( final_beat > end_beats ) {
2022-02-18 20:01:52 -05:00
/* no more events to come before final_beat,
* and that is beyond the end of this : : run ( )
* call . Not finished with playout yet , but
* all frames covered .
*/
2022-02-18 14:49:15 -05:00
nframes = 0 ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 not done with playout, all frames covered \n " , index ( ) ) ) ;
} else {
/* finishing up playout */
samplepos_t final_processed_sample = tmap - > sample_at ( timepos_t ( final_beat ) ) ;
nframes = orig_nframes - ( final_processed_sample - start_sample ) ;
_loop_cnt + + ;
_state = Stopped ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 playout done, nf = %2 fb %3 fs %4 %5 LC %6 \n " , index ( ) , nframes , final_beat , final_processed_sample , start_sample , _loop_cnt ) ) ;
2022-01-11 13:32:35 -05:00
}
2021-12-27 17:08:20 -05:00
} else {
2022-01-11 13:32:35 -05:00
2022-02-15 17:27:55 -05:00
samplepos_t final_processed_sample = tmap - > sample_at ( timepos_t ( final_beat ) ) ;
nframes = orig_nframes - ( final_processed_sample - start_sample ) ;
2022-01-11 13:32:35 -05:00
_loop_cnt + + ;
_state = Stopped ;
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 reached final event, now stopped, nf = %2 fb %3 fs %4 %5 LC %6 \n " , index ( ) , nframes , final_beat , final_processed_sample , start_sample , _loop_cnt ) ) ;
2021-12-27 17:08:20 -05:00
}
2022-02-15 17:27:55 -05:00
2021-12-27 17:08:20 -05:00
} else {
/* we didn't reach the end of the MIDI data, ergo we covered
the entire timespan passed into us .
*/
2022-02-08 18:59:36 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 did not reach end, nframes left at %2, next event is %3 \n " , index ( ) , nframes , * iter ) ) ;
2021-12-28 20:01:41 -05:00
nframes = 0 ;
2021-12-27 17:08:20 -05:00
}
2022-01-12 16:12:15 -05:00
const samplecnt_t covered_frames = orig_nframes - nframes ;
2021-12-28 20:01:41 -05:00
if ( _state = = Stopped | | _state = = Stopping ) {
2022-02-08 18:59:36 -05:00
when_stopped_during_run ( bufs , dest_offset + covered_frames ) ;
2021-11-30 09:57:34 -05:00
}
2022-02-14 11:52:43 -05:00
process_index + = covered_frames ;
2022-01-12 16:12:15 -05:00
return covered_frames ;
2021-10-20 18:19:09 -04:00
}
2021-08-08 21:08:16 -04:00
/**************/
void
Trigger : : make_property_quarks ( )
{
2021-11-15 14:27:32 -05:00
Properties : : running . property_id = g_quark_from_static_string ( X_ ( " running " ) ) ;
2021-08-08 21:08:16 -04:00
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for running = %1 \n " , Properties : : running . property_id ) ) ;
2021-12-14 10:32:06 -05:00
Properties : : follow_count . property_id = g_quark_from_static_string ( X_ ( " follow-count " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for follow_count = %1 \n " , Properties : : follow_count . property_id ) ) ;
2022-01-16 11:54:17 -05:00
Properties : : use_follow_length . property_id = g_quark_from_static_string ( X_ ( " use-follow-length " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for use_follow_length = %1 \n " , Properties : : use_follow_length . property_id ) ) ;
2022-01-13 15:30:04 -05:00
Properties : : follow_length . property_id = g_quark_from_static_string ( X_ ( " follow-length " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for follow_length = %1 \n " , Properties : : follow_length . 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 ) ) ;
2021-12-14 14:09:43 -05:00
Properties : : velocity_effect . property_id = g_quark_from_static_string ( X_ ( " velocity-effect " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for velocity_effect = %1 \n " , Properties : : velocity_effect . property_id ) ) ;
Properties : : follow_action_probability . property_id = g_quark_from_static_string ( X_ ( " follow-action-probability " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for follow_action_probability = %1 \n " , Properties : : follow_action_probability . 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-12-17 13:40:05 -05:00
Properties : : gain . property_id = g_quark_from_static_string ( X_ ( " gain " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for gain = %1 \n " , Properties : : gain . property_id ) ) ;
2021-12-15 19:06:25 -05:00
Properties : : stretchable . property_id = g_quark_from_static_string ( X_ ( " stretchable " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for stretchable = %1 \n " , Properties : : stretchable . property_id ) ) ;
2022-01-26 12:11:27 -05:00
Properties : : cue_isolated . property_id = g_quark_from_static_string ( X_ ( " cue_isolated " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for cue_isolated = %1 \n " , Properties : : cue_isolated . property_id ) ) ;
2022-02-28 10:50:46 -05:00
Properties : : allow_patch_changes . property_id = g_quark_from_static_string ( X_ ( " allow_patch_changes " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for allow_patch_changes = %1 \n " , Properties : : allow_patch_changes . property_id ) ) ;
2022-01-11 17:27:03 -05:00
Properties : : stretch_mode . property_id = g_quark_from_static_string ( X_ ( " stretch_mode " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for stretch_mode = %1 \n " , Properties : : stretch_mode . property_id ) ) ;
2022-02-02 14:45:57 -05:00
Properties : : patch_change . property_id = g_quark_from_static_string ( X_ ( " patch_change " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for patch_change = %1 \n " , Properties : : patch_change . property_id ) ) ;
2022-02-03 15:07:04 -05:00
Properties : : channel_map . property_id = g_quark_from_static_string ( X_ ( " channel_map " ) ) ;
DEBUG_TRACE ( DEBUG : : Properties , string_compose ( " quark for channel_map = %1 \n " , Properties : : channel_map . property_id ) ) ;
2021-08-08 21:08:16 -04:00
}
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-10 17:55:58 -05:00
std : : atomic < int > TriggerBox : : active_trigger_boxes ( 0 ) ;
2021-11-10 10:45:38 -05:00
TriggerBoxThread * TriggerBox : : worker = 0 ;
2022-01-21 11:30:12 -05:00
CueRecords TriggerBox : : cue_records ( 256 ) ;
2022-01-23 20:34:07 -05:00
std : : atomic < bool > TriggerBox : : _cue_recording ( false ) ;
2022-01-21 11:57:40 -05:00
PBD : : Signal0 < void > TriggerBox : : CueRecordingChanged ;
2022-02-11 08:31:03 -05:00
typedef std : : map < boost : : shared_ptr < Region > , boost : : shared_ptr < Trigger : : UIState > > RegionStateMap ;
RegionStateMap enqueued_state_map ;
2021-11-10 10:45:38 -05:00
void
TriggerBox : : init ( )
{
worker = new TriggerBoxThread ;
TriggerBoxThread : : init_request_pool ( ) ;
init_pool ( ) ;
}
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 )
2022-02-01 20:03:51 -05:00
, tracker ( dt = = DataType : : MIDI ? new MidiStateTracker : 0 )
2021-08-08 21:08:16 -04:00
, _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-12-06 10:57:06 -05:00
, _currently_playing ( 0 )
2021-09-05 01:19:47 -04:00
, _stop_all ( false )
2022-01-06 02:23:27 -05:00
, _active_scene ( - 1 )
2022-02-01 00:40:05 -05:00
, _active_slots ( 0 )
2022-02-08 19:05:15 -05:00
, _locate_armed ( false )
2022-02-19 10:53:30 -05:00
, _cancel_locate_armed ( false )
2022-05-12 10:15:15 -04:00
, _fast_forwarding ( false )
2022-02-08 19:05:15 -05:00
2021-11-04 11:50:34 -04:00
, requests ( 1024 )
2021-08-08 21:08:16 -04:00
{
2021-12-13 18:24:13 -05:00
set_display_to_user ( false ) ;
2021-08-08 21:08:16 -04:00
/* default number of possible triggers. call ::add_trigger() to increase */
if ( _data_type = = DataType : : AUDIO ) {
2021-12-21 15:37:47 -05:00
for ( uint32_t n = 0 ; n < default_triggers_per_box ; + + n ) {
all_triggers . push_back ( boost : : make_shared < AudioTrigger > ( n , * this ) ) ;
2021-08-08 21:08:16 -04:00
}
2021-10-20 18:53:52 -04:00
} else {
2021-12-21 15:37:47 -05:00
for ( uint32_t n = 0 ; n < default_triggers_per_box ; + + n ) {
all_triggers . push_back ( boost : : make_shared < MIDITrigger > ( n , * this ) ) ;
2021-10-20 18:53:52 -04:00
}
2021-08-08 21:08:16 -04:00
}
2021-12-21 19:36:39 -05:00
while ( pending . size ( ) < all_triggers . size ( ) ) {
pending . push_back ( std : : atomic < Trigger * > ( 0 ) ) ;
}
2021-12-21 17:15:06 -05:00
2021-10-09 18:58:58 -04:00
Config - > ParameterChanged . connect_same_thread ( * this , boost : : bind ( & TriggerBox : : parameter_changed , this , _1 ) ) ;
2022-05-28 01:05:34 -04:00
_session . config . ParameterChanged . connect_same_thread ( * this , boost : : bind ( & TriggerBox : : parameter_changed , this , _1 ) ) ;
2021-08-08 21:08:16 -04:00
}
2022-01-21 11:57:40 -05:00
void
TriggerBox : : set_cue_recording ( bool yn )
{
if ( yn ! = _cue_recording ) {
_cue_recording = yn ;
CueRecordingChanged ( ) ;
}
}
2022-02-19 10:53:30 -05:00
void
TriggerBox : : parameter_changed ( std : : string const & param )
{
if ( param = = X_ ( " default-trigger-input-port " ) ) {
reconnect_to_default ( ) ;
} else if ( param = = " cue-behavior " ) {
2022-05-28 01:05:34 -04:00
const bool follow = ( _session . config . get_cue_behavior ( ) & FollowCues ) ;
if ( ! follow ) {
2022-02-19 10:53:30 -05:00
cancel_locate_armed ( ) ;
}
}
}
void
TriggerBox : : cancel_locate_armed ( )
{
_cancel_locate_armed = true ;
}
2022-02-07 17:44:08 -05:00
void
TriggerBox : : fast_forward ( CueEvents const & cues , samplepos_t transport_position )
{
2022-02-11 17:22:34 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1: ffwd to %2 \n " , order ( ) , transport_position ) ) ;
2022-02-19 10:53:30 -05:00
2022-02-19 11:55:59 -05:00
if ( ! ( _session . config . get_cue_behavior ( ) & FollowCues ) ) {
2022-02-19 10:53:30 -05:00
/* do absolutely nothing */
return ;
}
if ( cues . empty ( ) | | ( cues . front ( ) . time > transport_position ) ) {
2022-02-18 14:54:50 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1: nothing to be done, cp = %2 \n " , order ( ) , _currently_playing ) ) ;
2022-02-19 10:53:30 -05:00
cancel_locate_armed ( ) ;
2022-02-11 17:22:34 -05:00
if ( tracker ) {
tracker - > reset ( ) ;
}
2022-02-07 17:44:08 -05:00
return ;
}
2022-05-12 10:15:15 -04:00
PBD : : Unwinder < bool > uw ( _fast_forwarding , true ) ;
2022-02-11 12:33:11 -05:00
2022-02-07 17:44:08 -05:00
using namespace Temporal ;
TempoMap : : SharedPtr tmap ( TempoMap : : use ( ) ) ;
CueEvents : : const_iterator c = cues . begin ( ) ;
samplepos_t pos = c - > time ;
TriggerPtr prev ;
Temporal : : BBT_Time start_bbt ;
samplepos_t start_samples ;
2022-03-19 17:24:41 -04:00
Temporal : : Beats elen ;
2022-02-07 17:44:08 -05:00
while ( pos < transport_position & & c ! = cues . end ( ) & & c - > time < transport_position ) {
CueEvents : : const_iterator nxt_cue = c ; + + nxt_cue ;
2022-02-11 15:36:58 -05:00
if ( c - > cue = = INT32_MAX ) {
2022-02-11 13:33:06 -05:00
/* "stop all cues" marker encountered. This ends the
duration of whatever slot might have been running
when we hit the cue .
*/
prev . reset ( ) ;
c = nxt_cue ;
continue ;
}
2022-02-07 17:44:08 -05:00
TriggerPtr trig ( all_triggers [ c - > cue ] ) ;
2022-02-11 11:11:43 -05:00
if ( trig - > cue_isolated ( ) ) {
c = nxt_cue ;
2022-03-19 17:24:41 -04:00
pos = c - > time ;
2022-02-11 11:11:43 -05:00
continue ;
}
if ( ! trig - > region ( ) ) {
/* the cue-identified slot is empty for this
triggerbox . This effectively ends the duration of
whatever slot might have been running when we hit
the cue .
*/
prev . reset ( ) ;
2022-02-07 17:44:08 -05:00
c = nxt_cue ;
2022-03-19 17:24:41 -04:00
pos = c - > time ;
2022-02-07 17:44:08 -05:00
continue ;
}
samplepos_t limit ;
if ( nxt_cue = = cues . end ( ) ) {
limit = transport_position ;
} else {
limit = nxt_cue - > time ;
}
bool will_start = true ;
start_bbt = trig - > compute_start ( tmap , pos , limit , trig - > quantization ( ) , start_samples , will_start ) ;
if ( ! will_start ) {
/* trigger will not start between this cue and the next */
c = nxt_cue ;
pos = limit ;
continue ;
}
/* we now consider this trigger to be running. Let's see when
* it ends . . .
*/
2022-03-19 17:24:41 -04:00
samplepos_t trig_ends_at = trig - > compute_end ( tmap , start_bbt , start_samples , elen ) . samples ( ) ;
2022-02-07 17:44:08 -05:00
if ( nxt_cue ! = cues . end ( ) & & trig_ends_at > = nxt_cue - > time ) {
/* trigger will be interrupted by next cue .
*
*/
trig_ends_at = tmap - > sample_at ( tmap - > bbt_at ( timepos_t ( nxt_cue - > time ) ) . round_up_to_bar ( ) ) ;
}
if ( trig_ends_at > = transport_position ) {
prev = trig ;
/* we're done. prev now indicates the trigger that
would have started most recently before the
transport position .
*/
break ;
}
int dnt = determine_next_trigger ( trig - > index ( ) ) ;
if ( dnt < 0 ) {
/* no trigger follows the current one. Back to
looking for another cue .
*/
c = nxt_cue ;
continue ;
}
prev = trig ;
pos = trig_ends_at ;
2022-02-08 19:05:15 -05:00
trig = all_triggers [ dnt ] ;
c = nxt_cue ;
2022-02-07 17:44:08 -05:00
}
if ( pos > = transport_position | | ! prev ) {
/* nothing to do */
2022-03-19 17:24:41 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1: no trigger to be rolled (%2 >= %3, prev = %4) \n " , order ( ) , pos , transport_position , prev ) ) ;
2022-02-14 14:59:24 -05:00
_currently_playing = 0 ;
2022-02-11 15:36:58 -05:00
_locate_armed = false ;
if ( tracker ) {
tracker - > reset ( ) ;
}
2022-02-07 17:44:08 -05:00
return ;
}
/* prev now points to a trigger that would start before
* transport_position and would still be running at
* transport_position . We need to run it in a special mode that ensures
* that
*
* 1 ) for MIDI , we know the state at transport position
* 2 ) for audio , the stretcher is in the correct state
*/
2022-03-19 17:24:41 -04:00
/* find the closest start (retrigger) position for this trigger */
if ( start_samples < transport_position ) {
samplepos_t s = start_samples ;
BBT_Time ns = start_bbt ;
do {
start_samples = s ;
ns = tmap - > bbt_walk ( ns , BBT_Offset ( 0 , elen . get_beats ( ) , elen . get_ticks ( ) ) ) ;
s = tmap - > sample_at ( ns ) ;
} while ( s < transport_position ) ;
}
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1: roll trigger %2 from %3 to %4 \n " , order ( ) , prev - > index ( ) , start_samples , transport_position ) ) ;
2022-02-08 19:05:15 -05:00
prev - > start_and_roll_to ( start_samples , transport_position ) ;
2022-02-07 17:44:08 -05:00
_currently_playing = prev ;
2022-02-08 19:05:15 -05:00
_locate_armed = true ;
2022-02-07 17:44:08 -05:00
/* currently playing is now ready to keep running at transport position
*
* Note that a MIDITrigger will have set a flag so that when we call
* : : run ( ) again , it will dump its current MIDI state before anything
* else .
*/
}
2021-12-21 17:15:06 -05:00
void
TriggerBox : : set_region ( uint32_t slot , boost : : shared_ptr < Region > region )
{
/* This is called from our worker thread */
2021-12-21 19:36:39 -05:00
Trigger * t ;
2021-12-21 17:15:06 -05:00
switch ( _data_type ) {
case DataType : : AUDIO :
2021-12-21 19:36:39 -05:00
t = new AudioTrigger ( slot , * this ) ;
2021-12-21 17:15:06 -05:00
break ;
case DataType : : MIDI :
2021-12-21 19:36:39 -05:00
t = new MIDITrigger ( slot , * this ) ;
2021-12-21 17:15:06 -05:00
break ;
2021-12-30 20:47:50 -05:00
default :
return ;
2021-12-21 17:15:06 -05:00
}
2022-02-11 08:31:03 -05:00
/* set_region_in_worker_thread estimates a tempo, and makes some guesses about whether a clip is a one-shot or looping*/
2021-12-22 09:42:52 -05:00
t - > set_region_in_worker_thread ( region ) ;
2021-12-21 17:15:06 -05:00
2022-02-11 08:31:03 -05:00
/* if we are the target of a drag&drop from another Trigger Slot, we need the name, color and other properties to carry over with the region */
RegionStateMap : : iterator rs ;
if ( ( rs = enqueued_state_map . find ( region ) ) ! = enqueued_state_map . end ( ) ) {
Trigger : : UIState copy ; copy = * ( rs - > second ) ;
t - > set_ui_state ( * ( rs - > second ) ) ;
enqueued_state_map . erase ( rs ) ;
2022-01-27 14:13:03 -05:00
}
2022-01-26 18:57:39 -05:00
//* always preserve the launch-style and cue_isolate status. It's likely to be right, but if it's wrong the user can "see" it's wrong anyway */
t - > set_launch_style ( all_triggers [ slot ] - > launch_style ( ) ) ;
t - > set_cue_isolated ( all_triggers [ slot ] - > cue_isolated ( ) ) ;
//* if the existing slot seems to be part of a FA 'arrangement', preserve the settings */
if ( all_triggers [ slot ] - > follow_action0 ( ) . is_arrangement ( ) ) {
t - > set_follow_action0 ( all_triggers [ slot ] - > follow_action0 ( ) ) ;
t - > set_follow_action1 ( all_triggers [ slot ] - > follow_action1 ( ) ) ;
t - > set_follow_action_probability ( all_triggers [ slot ] - > follow_action_probability ( ) ) ;
t - > set_quantization ( all_triggers [ slot ] - > quantization ( ) ) ;
//color ?
2022-03-21 13:07:43 -04:00
t - > set_follow_count ( all_triggers [ slot ] - > follow_count ( ) ) ;
t - > set_follow_length ( all_triggers [ slot ] - > follow_length ( ) ) ;
t - > set_use_follow_length ( all_triggers [ slot ] - > use_follow_length ( ) ) ;
2022-01-26 18:57:39 -05:00
}
2021-12-21 17:15:06 -05:00
/* XXX what happens if pending is already set? */
set_pending ( slot , t ) ;
}
void
2021-12-21 19:36:39 -05:00
TriggerBox : : set_pending ( uint32_t slot , Trigger * t )
{
all_triggers [ slot ] - > set_pending ( t ) ;
}
void
TriggerBox : : maybe_swap_pending ( uint32_t slot )
2021-12-21 17:15:06 -05:00
{
2021-12-22 00:35:59 -05:00
/* This is called synchronously with process() (i.e. in an RT process
thread ) and so it is impossible for any Triggers in this TriggerBox
to be invoked while this executes .
*/
2021-12-21 19:36:39 -05:00
Trigger * p = 0 ;
2022-02-01 00:40:05 -05:00
bool empty_changed = false ;
2021-12-21 19:36:39 -05:00
p = all_triggers [ slot ] - > swap_pending ( p ) ;
if ( p ) {
2021-12-22 00:35:59 -05:00
if ( p = = Trigger : : MagicClearPointerValue ) {
2022-02-01 00:40:05 -05:00
if ( all_triggers [ slot ] - > region ( ) ) {
if ( _active_slots ) {
_active_slots - - ;
}
if ( _active_slots = = 0 ) {
empty_changed = true ;
}
}
2021-12-22 00:35:59 -05:00
all_triggers [ slot ] - > clear_region ( ) ;
} else {
2022-02-01 00:40:05 -05:00
if ( ! all_triggers [ slot ] - > region ( ) ) {
if ( _active_slots = = 0 ) {
empty_changed = true ;
}
_active_slots + + ;
}
2021-12-27 13:24:21 -05:00
/* Note use of a custom delete function. We cannot
delete the old trigger from the RT context where the
trigger swap will happen , so we will ask the trigger
helper thread to take care of it .
*/
2021-12-24 17:00:58 -05:00
all_triggers [ slot ] . reset ( p , Trigger : : request_trigger_delete ) ;
2021-12-22 00:35:59 -05:00
TriggerSwapped ( slot ) ; /* EMIT SIGNAL */
}
2021-12-21 19:36:39 -05:00
}
2022-02-01 00:40:05 -05:00
if ( empty_changed ) {
EmptyStatusChanged ( ) ; /* EMIT SIGNAL */
}
2021-12-21 17:15:06 -05: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
2021-12-21 15:37:47 -05:00
TriggerBox : : queue_explict ( uint32_t n )
2021-09-04 12:37:36 -04:00
{
2021-12-21 15:37:47 -05:00
assert ( n < all_triggers . size ( ) ) ;
explicit_queue . write ( & n , 1 ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " explicit queue %1, EQ = %2 \n " , n , explicit_queue . read_space ( ) ) ) ;
2021-09-05 01:19:47 -04:00
2021-12-06 10:57:06 -05:00
if ( _currently_playing ) {
_currently_playing - > unbang ( ) ;
2021-09-05 01:19:47 -04:00
}
2021-09-04 12:37:36 -04:00
}
2021-12-21 15:37:47 -05:00
TriggerPtr
2021-09-04 12:37:36 -04:00
TriggerBox : : get_next_trigger ( )
{
2021-12-21 15:37:47 -05:00
uint32_t n ;
2021-09-04 12:37:36 -04:00
2021-12-21 15:37:47 -05:00
if ( explicit_queue . read ( & n , 1 ) = = 1 ) {
TriggerPtr r = trigger ( n ) ;
2021-11-29 23:50:19 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " next trigger from explicit queue = %1 \n " , r - > index ( ) ) ) ;
2021-09-04 12:37:36 -04:00
return r ;
}
return 0 ;
}
2022-01-27 13:45:29 -05:00
TriggerPtr
TriggerBox : : trigger_by_id ( PBD : : ID check )
{
for ( uint64_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
if ( trigger ( n ) - > id ( ) = = check ) {
return trigger ( n ) ;
}
}
return TriggerPtr ( ) ;
}
void
2022-02-11 08:31:03 -05:00
TriggerBox : : enqueue_trigger_state_for_region ( boost : : shared_ptr < Region > region , boost : : shared_ptr < Trigger : : UIState > state )
2022-01-27 14:13:03 -05:00
{
2022-02-11 08:31:03 -05:00
enqueued_state_map . insert ( std : : make_pair ( region , state ) ) ;
2022-01-27 14:13:03 -05:00
}
2021-11-10 10:45:38 -05:00
void
2021-12-21 15:37:47 -05:00
TriggerBox : : set_from_selection ( uint32_t slot , boost : : shared_ptr < Region > region )
2021-10-24 11:25:11 -04:00
{
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 ( ) ) {
2021-11-10 10:45:38 -05:00
return ;
2021-10-24 11:25:11 -04:00
}
2021-11-10 10:45:38 -05:00
all_triggers [ slot ] - > set_region ( region ) ;
2021-10-24 11:25:11 -04:00
}
2021-11-10 10:45:38 -05:00
void
2021-12-21 15:37:47 -05:00
TriggerBox : : set_from_path ( uint32_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 ( ) ) {
2021-11-10 10:45:38 -05:00
return ;
2021-10-11 19:56:56 -04:00
}
2021-08-08 21:08:16 -04:00
2021-12-18 11:31:44 -05:00
const DataType source_type = SMFSource : : safe_midi_file_extension ( path ) ? DataType : : MIDI : DataType : : AUDIO ;
if ( source_type ! = _data_type ) {
error < < string_compose ( _ ( " Cannot use %1 files in %2 slots " ) ,
( ( source_type = = DataType : : MIDI ) ? " MIDI " : " audio " ) ,
( ( source_type = = DataType : : MIDI ) ? " audio " : " MIDI " ) ) < < endmsg ;
return ;
}
2021-08-08 21:08:16 -04:00
try {
2021-12-15 20:18:18 -05:00
ImportStatus status ;
status . total = 1 ;
status . quality = SrcBest ;
status . freeze = false ;
status . paths . push_back ( path ) ;
status . replace_existing_source = false ;
status . split_midi_channels = false ;
2022-01-06 22:15:08 -05:00
status . midi_track_name_source = ARDOUR : : SMFTrackNumber ;
2021-08-08 21:08:16 -04:00
2021-12-15 20:18:18 -05:00
_session . import_files ( status ) ;
if ( status . cancel ) {
error < < string_compose ( _ ( " Cannot create source from %1 " ) , path ) < < endmsg ;
2021-11-10 10:45:38 -05:00
return ;
2021-08-08 21:08:16 -04:00
}
2021-12-18 10:29:35 -05:00
if ( status . sources . empty ( ) ) {
error < < string_compose ( _ ( " Could not create source from %1 " ) , path ) < < endmsg ;
return ;
}
2021-08-08 21:08:16 -04:00
SourceList src_list ;
2021-12-15 20:18:18 -05:00
for ( auto & src : status . sources ) {
src_list . push_back ( src ) ;
2021-08-08 21:08:16 -04:00
}
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 ) ;
} catch ( std : : exception & e ) {
cerr < < " loading sample from " < < path < < " failed: " < < e . what ( ) < < endl ;
2021-11-10 10:45:38 -05:00
return ;
2021-08-08 21:08:16 -04:00
}
}
TriggerBox : : ~ TriggerBox ( )
{
}
2021-09-05 01:19:47 -04:00
void
2021-12-22 19:27:07 -05:00
TriggerBox : : stop_all_immediately ( )
2021-09-05 01:19:47 -04:00
{
2021-11-29 23:50:19 -05:00
_requests . stop_all = true ;
2021-09-05 01:19:47 -04:00
}
2021-12-23 11:59:31 -05:00
void
TriggerBox : : clear_all_triggers ( )
{
for ( uint64_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
all_triggers [ n ] - > set_region ( boost : : shared_ptr < Region > ( ) ) ;
}
}
2021-12-22 13:28:48 -05:00
void
TriggerBox : : set_all_launch_style ( ARDOUR : : Trigger : : LaunchStyle ls )
{
for ( uint64_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
all_triggers [ n ] - > set_launch_style ( ls ) ;
}
}
void
2022-01-19 17:50:32 -05:00
TriggerBox : : set_all_follow_action ( ARDOUR : : FollowAction const & fa , uint32_t fa_n )
2021-12-22 13:28:48 -05:00
{
for ( uint64_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
2022-01-25 20:06:42 -05:00
if ( fa_n = = 0 ) {
all_triggers [ n ] - > set_follow_action0 ( fa ) ;
} else {
all_triggers [ n ] - > set_follow_action1 ( fa ) ;
}
2021-12-22 13:28:48 -05:00
}
}
void
TriggerBox : : set_all_probability ( int zero_to_hundred )
{
for ( uint64_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
all_triggers [ n ] - > set_follow_action_probability ( zero_to_hundred ) ;
}
}
void
TriggerBox : : set_all_quantization ( Temporal : : BBT_Offset const & q )
{
for ( uint64_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
all_triggers [ n ] - > set_quantization ( q ) ;
}
}
2021-08-08 21:08:16 -04:00
void
TriggerBox : : stop_all ( )
{
2021-12-22 19:27:07 -05:00
/* Stops all triggers as soon as possible */
2021-08-08 21:08:16 -04:00
/* XXX needs to be done with mutex or via thread-safe queue */
2021-11-29 23:50:19 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , " stop-all request received \n " ) ;
2021-12-21 15:37:47 -05:00
for ( uint32_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
2021-11-29 23:50:19 -05:00
all_triggers [ n ] - > request_stop ( ) ;
2021-08-08 21:08:16 -04:00
}
2021-09-05 01:19:47 -04:00
2021-11-29 23:50:19 -05:00
_stop_all = true ;
2021-11-10 17:55:58 -05:00
2021-11-29 23:50:19 -05:00
explicit_queue . reset ( ) ;
2021-08-08 21:08:16 -04:00
}
2021-12-22 19:27:07 -05:00
void
TriggerBox : : stop_all_quantized ( )
{
for ( uint32_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
all_triggers [ n ] - > unbang ( ) ;
}
}
2021-08-08 21:08:16 -04:00
void
TriggerBox : : drop_triggers ( )
{
Glib : : Threads : : RWLock : : WriterLock lm ( trigger_lock ) ;
2021-08-31 21:03:32 -04:00
all_triggers . clear ( ) ;
2021-08-08 21:08:16 -04:00
}
2021-12-21 15:37:47 -05:00
TriggerPtr
2021-08-08 21:08:16 -04:00
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
2022-01-03 19:22:34 -05:00
TriggerBox : : add_midi_sidechain ( )
2021-10-08 15:37:29 -04:00
{
2022-01-03 19:22:34 -05:00
assert ( owner ( ) ) ;
2021-10-08 15:37:29 -04:00
if ( ! _sidechain ) {
2022-01-03 19:22:34 -05:00
_sidechain . reset ( new SideChain ( _session , string_compose ( " %1/%2 " , owner ( ) - > name ( ) , name ( ) ) ) ) ;
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 ) {
2022-01-27 17:04:30 -05:00
if ( ! Config - > get_default_trigger_input_port ( ) . empty ( ) ) {
p - > connect ( Config - > get_default_trigger_input_port ( ) ) ;
}
2021-10-10 19:35:15 -04:00
} else {
error < < _ ( " Could not create port for trigger side-chain " ) < < endmsg ;
}
2021-10-08 15:37:29 -04:00
}
}
2022-01-03 19:22:34 -05:00
void
TriggerBox : : update_sidechain_name ( )
{
if ( ! _sidechain ) {
return ;
}
assert ( owner ( ) ) ;
_sidechain - > set_name ( string_compose ( " %1/%2 " , owner ( ) - > name ( ) , name ( ) ) ) ;
}
2021-08-08 21:08:16 -04:00
bool
TriggerBox : : can_support_io_configuration ( const ChanCount & in , ChanCount & out )
{
2021-12-15 20:15:58 -05:00
/* if this is an audio trigger, let it be known that we have at least 1 audio output.
*/
if ( _data_type = = DataType : : AUDIO ) {
out . set_audio ( std : : max ( in . n_audio ( ) , 1U ) ) ;
}
/* if this is a MIDI trigger, let it be known that we have at least 1 MIDI output.
*/
2021-12-13 18:50:31 -05:00
if ( _data_type = = DataType : : MIDI ) {
2021-12-15 20:15:58 -05:00
out . set_midi ( std : : max ( in . 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 ) {
2022-01-26 09:57:05 -05:00
_sidechain - > configure_io ( in , out + ChanCount ( DataType : : MIDI , 1 ) ) ;
2021-10-08 15:37:29 -04:00
}
2021-12-15 20:15:58 -05:00
bool ret = Processor : : configure_io ( in , out ) ;
if ( ret ) {
2021-12-21 15:37:47 -05:00
for ( uint32_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
2021-12-15 20:15:58 -05:00
all_triggers [ n ] - > io_change ( ) ;
}
}
2021-12-16 08:47:10 -05:00
return ret ;
2021-08-08 21:08:16 -04:00
}
void
2021-12-21 15:37:47 -05:00
TriggerBox : : add_trigger ( TriggerPtr trigger )
2021-08-08 21:08:16 -04:00
{
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 )
{
2022-01-26 09:57:32 -05:00
/* check MIDI port input buffer for triggers. This is always the last
* MIDI buffer of the BufferSet
*/
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05:00
MidiBuffer & mb ( bufs . get_midi ( bufs . count ( ) . n_midi ( ) - 1 /* due to zero-based index*/ ) ) ;
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05:00
for ( MidiBuffer : : iterator ev = mb . begin ( ) ; ev ! = mb . end ( ) ; + + ev ) {
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05:00
if ( ! ( * ev ) . is_note ( ) ) {
continue ;
}
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05:00
int trigger_number = note_to_trigger ( ( * ev ) . note ( ) , ( * ev ) . channel ( ) ) ;
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05: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-13 11:07:07 -04:00
2022-01-26 09:57:32 -05:00
if ( trigger_number < 0 ) {
/* not for us */
continue ;
}
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05:00
if ( trigger_number > = ( int ) all_triggers . size ( ) ) {
continue ;
}
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05:00
TriggerPtr t = all_triggers [ trigger_number ] ;
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05:00
if ( ! t ) {
continue ;
}
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05:00
if ( ( * ev ) . is_note_on ( ) ) {
2021-08-08 21:08:16 -04:00
2022-01-26 12:11:27 -05:00
if ( t - > velocity_effect ( ) ! = 0.0 ) {
2022-01-26 09:57:32 -05:00
/* 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 .
2021-10-19 14:52:32 -04:00
*/
2022-01-26 12:11:27 -05:00
t - > set_velocity_gain ( 1.0 - ( t - > velocity_effect ( ) * ( * ev ) . velocity ( ) / 127.f ) ) ;
2022-01-26 09:57:32 -05:00
}
t - > bang ( ) ;
2021-08-08 21:08:16 -04:00
2022-01-26 09:57:32 -05:00
} else if ( ( * ev ) . is_note_off ( ) ) {
2021-10-08 15:37:29 -04:00
2022-01-26 09:57:32 -05: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 .
*/
2021-11-29 23:50:19 -05:00
/* STEP ONE: are we actually active? */
if ( ! check_active ( ) ) {
return ;
2021-12-03 07:52:51 -05:00
}
2021-10-11 10:50:58 -04:00
2022-03-17 14:20:20 -04:00
if ( _session . transport_locating ( ) ) {
/* nothing to do here at all. We do not run triggers while
locate is still taking place .
*/
return ;
}
2021-12-31 14:51:54 -05:00
# ifndef NDEBUG
{
Temporal : : TempoMap : : SharedPtr __tmap ( Temporal : : TempoMap : : use ( ) ) ;
const Temporal : : Beats __start_beats ( timepos_t ( start_sample ) . beats ( ) ) ;
const Temporal : : Beats __end_beats ( timepos_t ( end_sample ) . beats ( ) ) ;
const double __bpm = __tmap - > quarters_per_minute_at ( timepos_t ( __start_beats ) ) ;
2022-02-08 19:05:15 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " **** Triggerbox::run() for %6, ss %1 es %2 sb %3 eb %4 bpm %5 nf %7 \n " , start_sample , end_sample , __start_beats , __end_beats , __bpm , order ( ) , nframes ) ) ;
2021-12-31 14:51:54 -05:00
}
# endif
2021-11-29 23:50:19 -05:00
bool allstop = _requests . stop_all . exchange ( false ) ;
/* STEP TWO: if latency compensation tells us that we haven't really
* started yet , do nothing , because we can ' t make sense of a negative
* start sample time w . r . t the tempo map .
*/
2021-08-08 21:08:16 -04:00
if ( start_sample < 0 ) {
return ;
}
2021-11-29 23:50:19 -05:00
/* STEP THREE: triggers in audio tracks need a MIDI sidechain to be
* able to receive inbound MIDI for triggering etc . This needs to run
* before anything else , since we may need data just received to launch
* a trigger ( or stop it )
*/
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
2022-01-27 13:36:04 -05:00
bool was_recorded ;
int32_t cue_bang = _session . first_cue_within ( start_sample , end_sample , was_recorded ) ;
2022-02-11 15:36:58 -05:00
if ( ! _cue_recording | | ! was_recorded ) {
if ( cue_bang = = INT32_MAX ) {
2022-02-11 17:22:34 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 sees STOP ALL! \n " , order ( ) ) ) ;
2022-02-11 15:36:58 -05:00
/* reached a "stop all cue-launched cues from playing"
* marker . The stop is quantized , not immediate .
*/
2022-02-21 23:06:17 -05:00
if ( _currently_playing ) {
2022-02-11 15:36:58 -05:00
_currently_playing - > unbang ( ) ;
}
2022-02-23 00:00:11 -05:00
_locate_armed = 0 ;
2022-02-11 15:36:58 -05:00
} else if ( cue_bang > = 0 ) {
2022-01-21 12:55:45 -05:00
_active_scene = cue_bang ;
2022-02-23 00:00:11 -05:00
_locate_armed = 0 ;
2022-01-21 12:55:45 -05:00
}
2022-01-06 02:23:27 -05:00
}
2021-09-04 12:37:36 -04:00
2021-11-29 23:50:19 -05:00
/* STEP SIX: if at this point there is an active cue, make it trigger
* our corresponding slot
*/
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 ( ) ) {
2022-01-25 20:06:42 -05:00
if ( ! all_triggers [ _active_scene ] - > cue_isolated ( ) ) {
2022-01-26 16:43:59 -05:00
if ( all_triggers [ _active_scene ] - > region ( ) ) {
all_triggers [ _active_scene ] - > bang ( ) ;
} else {
stop_all_quantized ( ) ; //empty slot, this should work as a Stop for the running clips
//TODO: can we set a flag so the UI reports that we are stopping?
}
2021-12-17 13:39:38 -05:00
}
2021-11-01 14:11:34 -04:00
}
}
2022-01-06 02:23:27 -05:00
/* STEP FOUR: handle any incoming requests from the GUI or other
* non - MIDI UIs
*/
process_requests ( bufs ) ;
/* STEP FIVE: handle any incoming MIDI requests
*/
process_midi_trigger_requests ( bufs ) ;
2021-11-29 23:50:19 -05:00
/* STEP SEVEN: let each slot process any individual state requests
*/
2021-09-04 12:37:36 -04:00
2021-12-21 15:37:47 -05:00
std : : vector < uint32_t > to_run ;
2021-08-08 21:08:16 -04:00
2022-02-09 19:25:59 -05:00
for ( auto & trig : all_triggers ) {
trig - > process_state_requests ( bufs , nframes - 1 ) ;
2021-09-05 01:19:47 -04:00
}
2021-08-08 21:08:16 -04:00
2022-01-06 02:23:27 -05:00
/* cue handling is over at this point, reset _active_scene to reflect this */
_active_scene = - 1 ;
2021-12-22 00:35:59 -05:00
if ( _currently_playing & & _currently_playing - > state ( ) = = Trigger : : Stopped ) {
_currently_playing = 0 ;
}
for ( uint32_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
if ( all_triggers [ n ] ! = _currently_playing ) {
maybe_swap_pending ( n ) ;
}
}
2021-11-29 23:50:19 -05:00
/* STEP EIGHT: if there is no active slot, see if there any queued up
*/
2021-09-05 12:40:58 -04:00
2021-12-29 15:06:10 -05:00
if ( ! _currently_playing & & ! allstop ) {
2021-12-06 10:57:06 -05:00
if ( ( _currently_playing = get_next_trigger ( ) ) ! = 0 ) {
2021-12-21 19:36:39 -05:00
maybe_swap_pending ( _currently_playing - > index ( ) ) ;
2022-02-02 17:04:22 -05:00
_currently_playing - > startup ( bufs , 0 ) ;
2021-12-06 10:57:06 -05:00
PropertyChanged ( Properties : : currently_playing ) ;
2021-11-10 17:55:58 -05:00
active_trigger_boxes . fetch_add ( 1 ) ;
2021-08-08 21:08:16 -04:00
}
}
2021-11-29 23:50:19 -05:00
/* STEP NINE: if we've been told to stop all slots, do so
*/
if ( allstop ) {
2021-11-10 17:55:58 -05:00
stop_all ( ) ;
}
2022-02-19 10:53:30 -05:00
if ( _locate_armed & & _cancel_locate_armed ) {
if ( _currently_playing ) {
_currently_playing - > shutdown ( bufs , 0 ) ;
_currently_playing = 0 ;
PropertyChanged ( Properties : : currently_playing ) ;
}
2022-03-19 17:24:41 -04:00
_cancel_locate_armed = false ;
} else if ( ! _locate_armed ) {
2022-02-19 10:53:30 -05:00
_cancel_locate_armed = false ;
}
2021-11-29 23:50:19 -05:00
/* STEP TEN: nothing to do?
*/
2021-12-06 10:57:06 -05:00
if ( ! _currently_playing ) {
2022-01-14 16:37:53 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , " nothing currently playing 1, reset stop_all to false \n " ) ;
_stop_all = false ;
2022-02-09 18:30:02 -05:00
/* nobody is active, but we should catch up on changes
* requested by the UI
*/
for ( auto & trig : all_triggers ) {
trig - > update_properties ( ) ;
}
2021-08-08 21:08:16 -04:00
return ;
}
2022-02-09 18:30:02 -05:00
/* some trigger is active, but the others should catch up on changes
* requested by the UI
*/
for ( auto & trig : all_triggers ) {
if ( trig ! = _currently_playing ) {
trig - > update_properties ( ) ;
}
}
2021-08-08 21:08:16 -04:00
/* transport must be active for triggers */
2022-02-08 19:05:15 -05:00
if ( ! _locate_armed ) {
if ( ! _session . transport_state_rolling ( ) & & ! allstop ) {
_session . start_transport_from_trigger ( ) ;
}
} else {
2022-02-10 21:59:42 -05:00
/* _locate_armed is true, so _currently_playing has been
fast - forwarded to our position , and is ready to
play . However , for MIDI triggers , we may need to dump a
bunch of state into our BufferSet to ensure that the state
of things matches the way it would have been had we actually
played the trigger / slot from the start .
*/
if ( _session . transport_state_rolling ( ) ) {
if ( tracker & & bufs . count ( ) . n_midi ( ) ) {
tracker - > flush ( bufs . get_midi ( 0 ) , 0 , true ) ;
}
_locate_armed = false ;
} else {
return ;
}
2021-08-08 21:08:16 -04:00
}
2021-11-29 23:50:19 -05:00
/* now get the information we need related to the tempo map and the
* timeline
*/
const Temporal : : Beats end_beats ( timepos_t ( end_sample ) . beats ( ) ) ;
2021-08-08 21:08:16 -04:00
Temporal : : TempoMap : : SharedPtr tmap ( Temporal : : TempoMap : : use ( ) ) ;
2021-12-02 13:27:18 -05:00
uint32_t max_chans = 0 ;
2021-12-21 15:37:47 -05:00
TriggerPtr nxt ;
2021-12-02 13:27:18 -05:00
pframes_t dest_offset = 0 ;
2021-11-15 14:27:32 -05:00
2021-11-29 23:50:19 -05:00
while ( nframes ) {
2021-11-15 14:27:32 -05:00
2021-11-29 23:50:19 -05:00
/* start can move if we have to switch triggers in mid-process cycle */
2021-08-08 21:08:16 -04:00
2021-11-29 23:50:19 -05:00
const Temporal : : Beats start_beats ( timepos_t ( start_sample ) . beats ( ) ) ;
const double bpm = tmap - > quarters_per_minute_at ( timepos_t ( start_beats ) ) ;
2021-09-05 12:40:58 -04:00
2021-12-02 13:27:18 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " nf loop, ss %1 es %2 sb %3 eb %4 bpm %5 \n " , start_sample , end_sample , start_beats , end_beats , bpm ) ) ;
2021-09-05 12:40:58 -04:00
2021-11-29 23:50:19 -05:00
/* see if there's another trigger explicitly queued */
2021-09-05 12:40:58 -04:00
2021-12-21 15:37:47 -05:00
RingBuffer < uint32_t > : : rw_vector rwv ;
2021-11-29 23:50:19 -05:00
explicit_queue . get_read_vector ( & rwv ) ;
2021-10-19 21:08:46 -04:00
2021-11-29 23:50:19 -05:00
if ( rwv . len [ 0 ] > 0 ) {
2021-09-05 12:40:58 -04:00
2021-11-30 09:57:34 -05: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
2021-11-29 23:50:19 -05:00
/* peek at it without dequeing it */
2021-09-05 12:40:58 -04:00
2021-12-21 15:37:47 -05:00
uint32_t n = * ( rwv . buf [ 0 ] ) ;
nxt = trigger ( n ) ;
2021-09-05 12:40:58 -04:00
2021-11-29 23:50:19 -05:00
/* if user triggered same clip, that will have been handled as
* it processed bang requests . Nothing to do here otherwise .
*/
2021-09-05 12:40:58 -04:00
2021-12-06 10:57:06 -05:00
if ( nxt ! = _currently_playing ) {
2021-09-05 12:40:58 -04:00
2021-11-29 23:50:19 -05:00
/* user has triggered a different slot than the currently waiting-to-play or playing slot */
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 .
*/
explicit_queue . increment_read_idx ( 1 ) ; /* consume the entry we peeked at */
2021-12-06 10:57:06 -05:00
nxt - > set_legato_offset ( _currently_playing - > current_pos ( ) ) ;
2021-12-21 19:36:39 -05:00
/* starting up next trigger, check for pending */
maybe_swap_pending ( n ) ;
nxt = trigger ( n ) ;
2021-11-29 23:50:19 -05:00
nxt - > jump_start ( ) ;
2022-01-06 18:50:55 -05:00
_currently_playing - > jump_stop ( bufs , dest_offset ) ;
2021-11-29 23:50:19 -05:00
/* and switch */
2021-12-06 10:57:06 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 => %2 switched to in legato mode \n " , _currently_playing - > index ( ) , nxt - > index ( ) ) ) ;
_currently_playing = nxt ;
PropertyChanged ( Properties : : currently_playing ) ;
2021-11-29 23:50:19 -05:00
} else {
2021-12-14 23:55:52 -05:00
/* no legato-switch */
if ( _currently_playing - > state ( ) = = Trigger : : Stopped ) {
2021-10-19 21:08:46 -04:00
2021-12-14 23:55:52 -05:00
explicit_queue . increment_read_idx ( 1 ) ; /* consume the entry we peeked at */
2021-12-21 19:36:39 -05:00
/* starting up next trigger, check for pending */
maybe_swap_pending ( n ) ;
nxt = trigger ( n ) ;
2022-02-02 17:04:22 -05:00
nxt - > startup ( bufs , dest_offset ) ;
2021-12-14 23:55:52 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 was finished, started %2 \n " , _currently_playing - > index ( ) , nxt - > index ( ) ) ) ;
_currently_playing = nxt ;
PropertyChanged ( Properties : : currently_playing ) ;
2022-02-20 11:20:47 -05:00
} else if ( _currently_playing - > state ( ) ! = Trigger : : WaitingToSwitch ) {
2021-12-14 23:55:52 -05:00
2022-02-18 14:56:51 -05:00
/* Notice that this condition
* leaves the next trigger to
* run in the queue .
*/
2021-12-14 23:55:52 -05:00
/* but just begin stoppingthe currently playing slot */
2022-02-20 11:20:47 -05:00
_currently_playing - > begin_switch ( nxt ) ;
2021-12-06 10:57:06 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " start stop for %1 before switching to %2 \n " , _currently_playing - > index ( ) , nxt - > index ( ) ) ) ;
2021-12-14 23:55:52 -05:00
2021-11-29 23:50:19 -05:00
}
2022-02-20 11:20:47 -05:00
2021-11-29 23:50:19 -05:00
}
2021-09-05 12:40:58 -04:00
}
}
2022-01-14 16:39:16 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " currently playing: %1, state now %2 stop all ? %3 \n " , _currently_playing - > name ( ) , enum_2_string ( _currently_playing - > state ( ) ) , _stop_all ) ) ;
2021-08-08 21:08:16 -04:00
2021-11-29 23:50:19 -05:00
/* if we're not in the process of stopping all active triggers,
* but the current one has stopped , decide which ( if any )
* trigger to play next .
*/
2021-12-06 10:57:06 -05:00
if ( _currently_playing - > state ( ) = = Trigger : : Stopped ) {
2021-12-15 13:19:36 -05:00
2021-12-15 13:03:47 -05:00
if ( ! _stop_all & & ! _currently_playing - > explicitly_stopped ( ) ) {
2021-12-15 13:19:36 -05:00
2021-12-06 10:57:06 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 has stopped, need next... \n " , _currently_playing - > name ( ) ) ) ;
2021-12-15 13:19:36 -05:00
2022-01-14 19:31:58 -05:00
if ( _currently_playing - > will_follow ( ) ) {
2021-12-15 13:19:36 -05:00
int n = determine_next_trigger ( _currently_playing - > index ( ) ) ;
2022-01-13 14:09:23 -05:00
Temporal : : BBT_Offset start_quantization ;
2021-12-15 13:19:36 -05:00
if ( n < 0 ) {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 finished, no next trigger \n " , _currently_playing - > name ( ) ) ) ;
_currently_playing = 0 ;
PropertyChanged ( Properties : : currently_playing ) ;
break ; /* no triggers to come next, break out of nframes loop */
}
2022-01-13 17:59:23 -05:00
if ( ( int ) _currently_playing - > index ( ) = = n ) {
2022-01-14 16:36:38 -05:00
start_quantization = Temporal : : BBT_Offset ( ) ;
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " switching to next trigger %1, will use start immediately \n " , all_triggers [ n ] - > name ( ) ) ) ;
2022-01-13 14:09:23 -05:00
} else {
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " switching to next trigger %1 \n " , all_triggers [ n ] - > name ( ) ) ) ;
}
2021-12-15 13:19:36 -05:00
_currently_playing = all_triggers [ n ] ;
2022-02-02 17:04:22 -05:00
_currently_playing - > startup ( bufs , dest_offset , start_quantization ) ;
2021-12-15 13:19:36 -05:00
PropertyChanged ( Properties : : currently_playing ) ;
} else {
_currently_playing = 0 ;
PropertyChanged ( Properties : : currently_playing ) ;
DEBUG_TRACE ( DEBUG : : Triggers , " currently playing was stopped, but stop_all was set, leaving nf loop \n " ) ;
/* leave nframes loop */
break ;
2021-11-29 23:50:19 -05:00
}
2021-12-15 13:19:36 -05:00
2021-11-29 23:50:19 -05:00
} else {
2021-12-15 13:19:36 -05:00
2021-12-06 10:57:06 -05:00
_currently_playing = 0 ;
PropertyChanged ( Properties : : currently_playing ) ;
2021-12-04 13:49:41 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , " currently playing was stopped, but stop_all was set, leaving nf loop \n " ) ;
2021-12-02 13:27:18 -05:00
/* leave nframes loop */
2021-11-29 23:50:19 -05:00
break ;
}
}
2021-08-08 21:08:16 -04:00
2021-11-29 23:50:19 -05:00
pframes_t frames_covered ;
2021-08-08 21:08:16 -04:00
2021-08-10 22:35:39 -04:00
2021-12-06 10:57:06 -05:00
boost : : shared_ptr < AudioRegion > ar = boost : : dynamic_pointer_cast < AudioRegion > ( _currently_playing - > region ( ) ) ;
2021-12-02 13:27:18 -05:00
if ( ar ) {
max_chans = std : : max ( ar - > n_channels ( ) , max_chans ) ;
2021-08-10 22:35:39 -04:00
}
2021-08-08 21:08:16 -04:00
2022-01-30 15:37:52 -05:00
frames_covered = _currently_playing - > run ( bufs , start_sample , end_sample , start_beats , end_beats , nframes , dest_offset , bpm ) ;
2021-09-05 01:19:47 -04:00
2021-11-29 23:50:19 -05:00
nframes - = frames_covered ;
start_sample + = frames_covered ;
2021-12-02 13:27:18 -05:00
dest_offset + = frames_covered ;
2021-08-08 21:08:16 -04:00
2021-12-02 13:27:18 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " trig %1 ran, covered %2 state now %3 nframes now %4 \n " ,
2021-12-06 10:57:06 -05:00
_currently_playing - > name ( ) , frames_covered , enum_2_string ( _currently_playing - > state ( ) ) , nframes ) ) ;
2021-08-08 21:08:16 -04:00
2021-11-29 23:50:19 -05:00
}
2021-08-08 21:08:16 -04:00
2021-12-06 10:57:06 -05:00
if ( ! _currently_playing ) {
2022-01-14 16:39:16 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , " nothing currently playing 2, reset stop_all to false \n " ) ;
2021-11-29 23:50:19 -05:00
_stop_all = false ;
}
2021-08-08 21:08:16 -04:00
2021-11-29 23:50:19 -05:00
/* audio buffer (channel) count determined by max of input and
2021-12-06 10:57:06 -05:00
* _currently_playing ' s channel count ( if it was audio ) .
2021-11-29 23:50:19 -05:00
*/
2021-09-04 12:37:36 -04:00
2021-11-29 23:50:19 -05:00
ChanCount cc ( DataType : : AUDIO , max_chans ) ;
2021-09-05 12:40:58 -04:00
2021-11-29 23:50:19 -05:00
/* MIDI buffer count not changed */
cc . set_midi ( bufs . count ( ) . n_midi ( ) ) ;
bufs . set_count ( cc ) ;
2021-09-04 12:37:36 -04:00
}
int
2021-12-21 15:37:47 -05:00
TriggerBox : : determine_next_trigger ( uint32_t current )
2021-08-10 22:35:39 -04:00
{
2021-12-21 15:37:47 -05:00
uint32_t n ;
uint32_t runnable = 0 ;
2022-01-20 13:01:00 -05:00
std : : vector < int32_t > possible_targets ;
possible_targets . reserve ( default_triggers_per_box ) ;
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-12-21 15:37:47 -05:00
for ( uint32_t n = 0 ; n < all_triggers . size ( ) ; + + n ) {
2021-08-10 22:35:39 -04:00
if ( all_triggers [ n ] - > region ( ) ) {
runnable + + ;
}
}
2021-12-22 00:35:59 -05:00
if ( runnable = = 0 | | ! all_triggers [ current ] - > region ( ) ) {
return - 1 ;
}
2022-01-26 12:11:27 -05:00
if ( all_triggers [ current ] - > follow_action0 ( ) . type = = FollowAction : : None ) {
2022-01-20 16:15:45 -05:00
/* when left follow action is disabled, no follow action */
return - 1 ;
}
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-15 10:04:08 -04:00
int r = _pcg . rand ( 100 ) ; // 0 .. 99
2022-01-19 17:50:32 -05:00
FollowAction fa ;
2021-08-12 01:03:53 -04:00
2021-12-23 16:54:34 -05:00
if ( r > = all_triggers [ current ] - > follow_action_probability ( ) ) {
2022-01-26 12:11:27 -05:00
fa = all_triggers [ current ] - > follow_action0 ( ) ;
2021-08-12 01:03:53 -04:00
} else {
2022-01-26 12:11:27 -05:00
fa = all_triggers [ current ] - > follow_action1 ( ) ;
2021-08-12 01:03:53 -04:00
}
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
*/
2022-01-19 17:50:32 -05:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " choose next trigger using follow action %1 given prob %2 and rnd %3 \n " , fa . to_string ( ) , all_triggers [ current ] - > follow_action_probability ( ) , r ) ) ;
2021-12-28 20:01:41 -05:00
2022-01-20 19:16:33 -05:00
if ( fa . type = = FollowAction : : Stop ) {
2021-09-04 12:37:36 -04:00
return - 1 ;
2022-01-20 19:16:33 -05:00
}
2021-08-12 01:03:53 -04:00
2022-01-20 19:16:33 -05:00
if ( runnable = = 1 ) {
/* 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 */
2022-01-19 17:50:32 -05:00
switch ( fa . type ) {
case FollowAction : : None :
2021-12-04 15:33:32 -05:00
return - 1 ;
2021-08-12 00:54:13 -04:00
2022-01-19 17:50:32 -05:00
case FollowAction : : Again :
2021-09-04 12:37:36 -04:00
return current ;
2021-08-12 00:54:13 -04:00
2022-01-19 17:50:32 -05:00
case FollowAction : : ForwardTrigger :
2021-08-10 22:35:39 -04:00
n = current ;
while ( true ) {
+ + n ;
if ( n > = all_triggers . size ( ) ) {
2021-11-29 23:50:19 -05:00
cerr < < " loop with n = " < < n < < " of " < < all_triggers . size ( ) < < endl ;
2021-08-10 22:35:39 -04:00
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 ;
2022-01-02 19:09:58 -05:00
2022-01-19 17:50:32 -05:00
case FollowAction : : ReverseTrigger :
2021-08-10 22:35:39 -04:00
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
2022-01-19 17:50:32 -05:00
case FollowAction : : FirstTrigger :
2021-08-10 22:35:39 -04:00
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 ;
2022-01-19 17:50:32 -05:00
case FollowAction : : 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 ;
2022-01-20 13:01:00 -05:00
case FollowAction : : JumpTrigger :
for ( std : : size_t n = 0 ; n < default_triggers_per_box ; + + n ) {
if ( fa . targets . test ( n ) & & all_triggers [ n ] - > region ( ) ) {
possible_targets . push_back ( n ) ;
}
}
if ( possible_targets . empty ( ) ) {
return 1 ;
}
return possible_targets [ _pcg . rand ( possible_targets . size ( ) ) ] ;
2021-08-10 22:35:39 -04:00
/* NOTREACHED */
2022-01-19 17:50:32 -05:00
case FollowAction : : Stop :
2021-08-10 22:35:39 -04:00
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 &
2022-04-06 23:56:32 -04:00
TriggerBox : : get_state ( ) const
2021-08-08 21:08:16 -04:00
{
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 ) ;
2022-04-06 23:56:32 -04:00
for ( auto const & t : all_triggers ) {
trigger_child - > add_child_nocopy ( t - > get_state ( ) ) ;
2021-08-31 18:46:19 -04:00
}
}
node . add_child_nocopy ( * trigger_child ) ;
2021-10-08 15:37:29 -04:00
if ( _sidechain ) {
2022-01-03 19:22:34 -05:00
node . add_child_nocopy ( _sidechain - > get_state ( ) ) ;
2021-10-08 15:37:29 -04:00
}
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 ) {
2021-12-21 15:37:47 -05:00
TriggerPtr trig ;
2021-08-31 21:03:32 -04:00
2021-12-27 14:04:39 -05:00
/* Note use of a custom delete function. We cannot
delete the old trigger from the RT context where the
trigger swap will happen , so we will ask the trigger
helper thread to take care of it .
*/
2021-08-31 21:03:32 -04:00
if ( _data_type = = DataType : : AUDIO ) {
2021-12-24 16:12:13 -05:00
trig . reset ( new AudioTrigger ( all_triggers . size ( ) , * this ) , Trigger : : request_trigger_delete ) ;
2021-08-31 21:03:32 -04:00
all_triggers . push_back ( trig ) ;
trig - > set_state ( * * t , version ) ;
2021-10-29 18:22:55 -04:00
} else if ( _data_type = = DataType : : MIDI ) {
2021-12-27 14:04:39 -05:00
trig . reset ( new MIDITrigger ( all_triggers . size ( ) , * this ) , Trigger : : request_trigger_delete ) ;
2021-10-29 18:22:55 -04:00
all_triggers . push_back ( trig ) ;
trig - > set_state ( * * t , version ) ;
2021-08-31 21:03:32 -04:00
}
2022-02-03 19:18:02 -05:00
if ( trig - > region ( ) ) {
_active_slots + + ;
}
2021-08-31 21:03:32 -04:00
}
}
2022-01-03 19:22:34 -05:00
/* sidechain is a Processor (IO) */
XMLNode * scnode = node . child ( Processor : : state_node_name . c_str ( ) ) ;
2021-10-08 15:37:29 -04:00
if ( scnode ) {
2022-01-03 19:22:34 -05:00
add_midi_sidechain ( ) ;
2021-10-08 15:37:29 -04:00
assert ( _sidechain ) ;
2022-01-24 15:21:07 -05:00
if ( ! regenerate_xml_or_string_ids ( ) ) {
_sidechain - > set_state ( * scnode , version ) ;
} else {
update_sidechain_name ( ) ;
}
2021-10-08 15:37:29 -04:00
}
2022-02-01 00:40:05 -05:00
/* Since _active_slots may have changed, we could consider sending
* EmptyStatusChanged , but for now we don ' t consider : : set_state ( ) to
* be used except at session load .
*/
2021-08-08 21:08:16 -04:00
return 0 ;
}
2021-10-07 10:03:46 -04:00
2021-10-09 18:58:58 -04:00
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
2021-11-04 16:00:16 -04:00
TriggerBox : : request_reload ( int32_t slot , void * ptr )
2021-11-04 13:16:22 -04:00
{
2021-11-04 16:00:16 -04:00
Request * r = new Request ( Request : : Reload ) ;
2021-11-04 13:16:22 -04:00
r - > slot = slot ;
2021-11-04 16:00:16 -04:00
r - > ptr = ptr ;
2021-11-04 13:16:22 -04:00
requests . write ( & r , 1 ) ;
}
void
2021-11-04 16:00:16 -04:00
TriggerBox : : process_requests ( BufferSet & bufs )
2021-11-04 13:16:22 -04:00
{
Request * r ;
while ( requests . read ( & r , 1 ) = = 1 ) {
2021-11-04 16:00:16 -04:00
process_request ( bufs , r ) ;
2021-11-04 13:16:22 -04:00
}
}
void
2021-11-04 16:00:16 -04:00
TriggerBox : : process_request ( BufferSet & bufs , Request * req )
2021-11-04 13:16:22 -04:00
{
switch ( req - > type ) {
case Request : : Use :
break ;
case Request : : Reload :
2021-11-04 16:00:16 -04:00
reload ( bufs , req - > slot , req - > ptr ) ;
2021-11-04 13:16:22 -04:00
break ;
}
delete req ; /* back to the pool, RT-safe */
}
2021-11-04 16:00:16 -04:00
void
TriggerBox : : reload ( BufferSet & bufs , int32_t slot , void * ptr )
{
2021-11-09 23:47:15 -05:00
if ( slot > = ( int32_t ) all_triggers . size ( ) ) {
2021-11-04 16:00:16 -04:00
return ;
}
all_triggers [ slot ] - > reload ( bufs , ptr ) ;
}
2021-12-06 11:06:46 -05:00
double
TriggerBox : : position_as_fraction ( ) const
{
2021-12-21 15:37:47 -05:00
TriggerPtr cp = _currently_playing ;
2021-12-06 11:06:46 -05:00
if ( ! cp ) {
return - 1 ;
}
return cp - > position_as_fraction ( ) ;
}
2022-02-09 19:20:38 -05:00
void
TriggerBox : : realtime_handle_transport_stopped ( )
{
Processor : : realtime_handle_transport_stopped ( ) ;
stop_all ( ) ;
_currently_playing = 0 ;
}
2022-02-07 17:44:08 -05:00
void
TriggerBox : : non_realtime_transport_stop ( samplepos_t now , bool /*flush*/ )
{
2022-03-19 17:24:41 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 (%3): non-realtime stop at %2 (lat-adjusted to %4 \n " , order ( ) , now , this , now + playback_offset ( ) ) ) ;
2022-03-17 14:20:20 -04:00
for ( auto & t : all_triggers ) {
t - > shutdown_from_fwd ( ) ;
}
2022-03-19 17:24:41 -04:00
if ( now ) {
now + = playback_offset ( ) ;
}
2022-02-07 17:44:08 -05:00
fast_forward ( _session . cue_events ( ) , now ) ;
}
void
TriggerBox : : non_realtime_locate ( samplepos_t now )
{
2022-03-19 17:24:41 -04:00
DEBUG_TRACE ( DEBUG : : Triggers , string_compose ( " %1 (%3): non-realtime locate at %2 (lat-adjusted to %4 \n " , order ( ) , now , this , now + playback_offset ( ) ) ) ;
2022-03-17 14:20:20 -04:00
for ( auto & t : all_triggers ) {
t - > shutdown_from_fwd ( ) ;
}
2022-03-19 17:24:41 -04:00
if ( now ) {
now + = playback_offset ( ) ;
}
2022-02-07 17:44:08 -05:00
fast_forward ( _session . cue_events ( ) , now ) ;
}
2022-03-17 14:19:11 -04:00
void
TriggerBox : : dump ( std : : ostream & ostr ) const
{
ostr < < " TriggerBox " < < order ( ) < < std : : endl ;
for ( auto const & t : all_triggers ) {
ostr < < " \t Trigger " < < t - > index ( ) < < " state " < < enum_2_string ( t - > state ( ) ) < < std : : endl ;
}
}
2021-11-09 23:47:15 -05:00
/* Thread */
2021-11-10 00:36:51 -05:00
MultiAllocSingleReleasePool * TriggerBoxThread : : Request : : pool = 0 ;
2021-11-09 23:47:15 -05:00
TriggerBoxThread : : TriggerBoxThread ( )
2021-11-10 00:36:51 -05:00
: requests ( 1024 )
, _xthread ( true )
2021-11-09 23:47:15 -05:00
{
if ( pthread_create_and_store ( " triggerbox thread " , & thread , _thread_work , this ) ) {
error < < _ ( " Session: could not create triggerbox thread " ) < < endmsg ;
throw failed_constructor ( ) ;
}
}
TriggerBoxThread : : ~ TriggerBoxThread ( )
{
void * status ;
2021-11-10 10:45:38 -05:00
char msg = ( char ) Quit ;
_xthread . deliver ( msg ) ;
2021-11-09 23:47:15 -05:00
pthread_join ( thread , & status ) ;
}
void *
TriggerBoxThread : : _thread_work ( void * arg )
{
SessionEvent : : create_per_thread_pool ( " tbthread events " , 4096 ) ;
pthread_set_name ( X_ ( " tbthread " ) ) ;
return ( ( TriggerBoxThread * ) arg ) - > thread_work ( ) ;
}
void *
TriggerBoxThread : : thread_work ( )
{
2021-12-22 09:42:27 -05:00
pthread_set_name ( X_ ( " Trigger Worker " ) ) ;
2021-11-10 00:36:51 -05:00
while ( true ) {
2021-11-09 23:47:15 -05:00
2021-11-10 00:36:51 -05:00
char msg ;
if ( _xthread . receive ( msg , true ) > = 0 ) {
2021-11-10 10:45:38 -05:00
if ( msg = = ( char ) Quit ) {
return ( void * ) 0 ;
abort ( ) ; /*NOTREACHED*/
2021-11-10 00:36:51 -05:00
}
2021-11-10 10:45:38 -05:00
Temporal : : TempoMap : : fetch ( ) ;
Request * req ;
2021-11-09 23:47:15 -05:00
2021-11-10 10:45:38 -05:00
while ( requests . read ( & req , 1 ) = = 1 ) {
switch ( req - > type ) {
case SetRegion :
2021-12-21 17:15:06 -05:00
req - > box - > set_region ( req - > slot , req - > region ) ;
2021-11-10 10:45:38 -05:00
break ;
2021-12-24 16:12:13 -05:00
case DeleteTrigger :
delete_trigger ( req - > trigger ) ;
break ;
2021-11-10 10:45:38 -05:00
default :
break ;
}
delete req ; /* back to pool */
}
}
2021-11-09 23:47:15 -05:00
}
2021-11-10 00:36:51 -05:00
return ( void * ) 0 ;
2021-11-09 23:47:15 -05:00
}
void
2021-11-10 00:36:51 -05:00
TriggerBoxThread : : queue_request ( Request * req )
2021-11-09 23:47:15 -05:00
{
2021-11-10 00:36:51 -05:00
char c = req - > type ;
2021-12-18 11:35:10 -05:00
/* Quit is handled by simply delivering the request type (1 byte), with
* no payload in the FIFO . See : : thread_work ( ) above .
*/
2021-11-10 00:36:51 -05:00
if ( req - > type ! = Quit ) {
if ( requests . write ( & req , 1 ) ! = 1 ) {
return ;
}
}
2021-12-18 11:33:51 -05:00
_xthread . deliver ( c ) ;
2021-11-09 23:47:15 -05:00
}
2021-11-10 00:36:51 -05:00
void *
TriggerBoxThread : : Request : : operator new ( size_t )
2021-11-09 23:47:15 -05:00
{
2021-11-10 00:36:51 -05:00
return pool - > alloc ( ) ;
2021-11-09 23:47:15 -05:00
}
void
2021-11-10 00:36:51 -05:00
TriggerBoxThread : : Request : : operator delete ( void * ptr , size_t )
2021-11-09 23:47:15 -05:00
{
2021-11-10 00:36:51 -05:00
pool - > release ( ptr ) ;
2021-11-09 23:47:15 -05:00
}
void
2021-11-10 00:36:51 -05:00
TriggerBoxThread : : Request : : init_pool ( )
2021-11-09 23:47:15 -05:00
{
2021-11-10 00:36:51 -05:00
pool = new MultiAllocSingleReleasePool ( X_ ( " TriggerBoxThreadRequests " ) , sizeof ( TriggerBoxThread : : Request ) , 1024 ) ;
2021-11-09 23:47:15 -05:00
}
2021-11-10 10:45:38 -05:00
void
2021-12-21 15:37:47 -05:00
TriggerBoxThread : : set_region ( TriggerBox & box , uint32_t slot , boost : : shared_ptr < Region > r )
2021-11-10 10:45:38 -05:00
{
TriggerBoxThread : : Request * req = new TriggerBoxThread : : Request ( TriggerBoxThread : : SetRegion ) ;
2021-12-21 15:37:47 -05:00
req - > box = & box ;
req - > slot = slot ;
2021-11-10 10:45:38 -05:00
req - > region = r ;
queue_request ( req ) ;
}
2021-12-24 16:12:13 -05:00
void
TriggerBoxThread : : request_delete_trigger ( Trigger * t )
{
TriggerBoxThread : : Request * req = new TriggerBoxThread : : Request ( DeleteTrigger ) ;
req - > trigger = t ;
queue_request ( req ) ;
}
void
TriggerBoxThread : : delete_trigger ( Trigger * t )
{
delete t ;
}