2021-07-17 15:21:45 -04:00
|
|
|
#include <iostream>
|
2021-08-10 22:35:39 -04:00
|
|
|
#include <cstdlib>
|
2021-08-13 18:08:17 -04:00
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
#include <glibmm.h>
|
|
|
|
|
2021-08-06 15:03:28 -04:00
|
|
|
#include <rubberband/RubberBandStretcher.h>
|
|
|
|
|
2021-07-28 00:39:42 -04:00
|
|
|
#include "pbd/basename.h"
|
2021-08-08 21:08:16 -04:00
|
|
|
#include "pbd/compose.h"
|
2021-07-19 11:57:31 -04:00
|
|
|
#include "pbd/failed_constructor.h"
|
2021-08-31 18:46:19 -04:00
|
|
|
#include "pbd/types_convert.h"
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-05 17:03:28 -04:00
|
|
|
#include "temporal/tempo.h"
|
|
|
|
|
2021-07-17 15:21:45 -04:00
|
|
|
#include "ardour/audioregion.h"
|
|
|
|
#include "ardour/audio_buffer.h"
|
2021-08-08 21:08:16 -04:00
|
|
|
#include "ardour/debug.h"
|
2021-07-17 15:21:45 -04:00
|
|
|
#include "ardour/midi_buffer.h"
|
2021-10-06 17:20:52 -04:00
|
|
|
#include "ardour/minibpm.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-09-01 00:35:14 -04:00
|
|
|
#include "ardour/source_factory.h"
|
2021-07-17 15:21:45 -04:00
|
|
|
#include "ardour/sndfilesource.h"
|
|
|
|
#include "ardour/triggerbox.h"
|
2021-09-26 23:59:15 -04:00
|
|
|
#include "ardour/types_convert.h"
|
2021-07-17 15:21:45 -04:00
|
|
|
|
2021-09-27 09:49:41 -04:00
|
|
|
#include "pbd/i18n.h"
|
|
|
|
|
2021-07-17 15:21:45 -04:00
|
|
|
using namespace PBD;
|
|
|
|
using namespace ARDOUR;
|
|
|
|
using std::string;
|
|
|
|
using std::cerr;
|
|
|
|
using std::endl;
|
|
|
|
|
2021-08-07 18:20:36 -04:00
|
|
|
namespace ARDOUR {
|
|
|
|
namespace Properties {
|
2021-09-26 23:59:15 -04:00
|
|
|
PBD::PropertyDescriptor<bool> use_follow;
|
2021-08-07 18:20:36 -04:00
|
|
|
PBD::PropertyDescriptor<bool> running;
|
2021-09-05 12:40:58 -04:00
|
|
|
PBD::PropertyDescriptor<bool> legato;
|
2021-09-29 20:00:55 -04:00
|
|
|
PBD::PropertyDescriptor<bool> quantization;
|
2021-10-01 20:39:06 -04:00
|
|
|
PBD::PropertyDescriptor<Trigger::LaunchStyle> launch_style;
|
|
|
|
PBD::PropertyDescriptor<Trigger::FollowAction> follow_action0;
|
|
|
|
PBD::PropertyDescriptor<Trigger::FollowAction> follow_action1;
|
2021-08-07 18:20:36 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
Trigger::Trigger (uint64_t n, TriggerBox& b)
|
2021-08-08 21:08:16 -04:00
|
|
|
: _box (b)
|
|
|
|
, _state (Stopped)
|
|
|
|
, _requested_state (None)
|
|
|
|
, _bang (0)
|
|
|
|
, _unbang (0)
|
|
|
|
, _index (n)
|
2021-08-10 22:35:39 -04:00
|
|
|
, _launch_style (Toggle)
|
2021-09-26 23:59:15 -04:00
|
|
|
, _use_follow (Properties::use_follow, true)
|
2021-08-13 18:03:36 -04:00
|
|
|
, _follow_action { NextTrigger, Stop }
|
2021-08-13 00:59:04 -04:00
|
|
|
, _follow_action_probability (100)
|
2021-08-08 21:08:16 -04:00
|
|
|
, _quantization (Temporal::BBT_Offset (0, 1, 0))
|
2021-09-26 23:59:15 -04:00
|
|
|
, _legato (Properties::legato, true)
|
2021-10-05 23:09:16 -04:00
|
|
|
, _stretch (1.0)
|
2021-09-10 15:04:49 -04:00
|
|
|
, _ui (0)
|
2021-08-07 18:20:36 -04:00
|
|
|
{
|
2021-09-26 23:59:15 -04:00
|
|
|
add_property (_legato);
|
|
|
|
add_property (_use_follow);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Trigger::set_use_follow (bool yn)
|
|
|
|
{
|
|
|
|
_use_follow = yn;
|
2021-09-28 19:45:35 -04:00
|
|
|
PropertyChanged (Properties::use_follow);
|
2021-08-07 18:20:36 -04:00
|
|
|
}
|
|
|
|
|
2021-08-31 13:53:24 -04:00
|
|
|
void
|
|
|
|
Trigger::set_name (std::string const & str)
|
|
|
|
{
|
|
|
|
_name = str;
|
|
|
|
}
|
|
|
|
|
2021-09-10 15:04:49 -04:00
|
|
|
void
|
|
|
|
Trigger::set_ui (void* p)
|
|
|
|
{
|
|
|
|
_ui = p;
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
void
|
|
|
|
Trigger::bang ()
|
2021-07-17 15:21:45 -04:00
|
|
|
{
|
2021-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-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
|
|
|
void
|
2021-09-10 15:12:14 -04:00
|
|
|
Trigger::set_follow_action (FollowAction f, uint64_t n)
|
2021-07-17 15:21:45 -04:00
|
|
|
{
|
2021-08-12 00:54:13 -04:00
|
|
|
assert (n < 2);
|
|
|
|
_follow_action[n] = f;
|
2021-10-01 20:39:06 -04:00
|
|
|
if (n == 0) {
|
|
|
|
PropertyChanged (Properties::follow_action0);
|
|
|
|
} else {
|
|
|
|
PropertyChanged (Properties::follow_action1);
|
|
|
|
}
|
2021-07-19 11:57:31 -04:00
|
|
|
}
|
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
void
|
2021-08-08 21:08:16 -04:00
|
|
|
Trigger::set_launch_style (LaunchStyle l)
|
2021-08-06 23:26:50 -04:00
|
|
|
{
|
2021-08-08 21:08:16 -04:00
|
|
|
_launch_style = l;
|
2021-08-11 20:25:46 -04:00
|
|
|
|
|
|
|
set_usable_length ();
|
2021-10-01 20:39:06 -04:00
|
|
|
PropertyChanged (Properties::launch_style);
|
2021-08-06 23:26:50 -04:00
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
XMLNode&
|
|
|
|
Trigger::get_state (void)
|
2021-07-19 11:57:31 -04:00
|
|
|
{
|
2021-08-08 21:08:16 -04:00
|
|
|
XMLNode* node = new XMLNode (X_("Trigger"));
|
2021-08-31 18:46:19 -04:00
|
|
|
|
2021-09-26 23:59:15 -04:00
|
|
|
for (OwnedPropertyList::iterator i = _properties->begin(); i != _properties->end(); ++i) {
|
|
|
|
i->second->get_value (*node);
|
|
|
|
}
|
|
|
|
|
2021-08-31 18:46:19 -04:00
|
|
|
node->set_property (X_("launch-style"), enum_2_string (_launch_style));
|
|
|
|
node->set_property (X_("follow-action-0"), enum_2_string (_follow_action[0]));
|
|
|
|
node->set_property (X_("follow-action-1"), enum_2_string (_follow_action[1]));
|
|
|
|
node->set_property (X_("quantization"), _quantization);
|
|
|
|
node->set_property (X_("name"), _name);
|
|
|
|
node->set_property (X_("index"), _index);
|
2021-10-05 23:09:16 -04:00
|
|
|
node->set_property (X_("stretch"), _stretch);
|
2021-08-31 18:46:19 -04:00
|
|
|
|
|
|
|
if (_region) {
|
|
|
|
node->set_property (X_("region"), _region->id());
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
return *node;
|
2021-07-19 11:57:31 -04:00
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
int
|
2021-08-31 20:36:16 -04:00
|
|
|
Trigger::set_state (const XMLNode& node, int version)
|
2021-07-19 11:57:31 -04:00
|
|
|
{
|
2021-09-26 23:59:15 -04:00
|
|
|
PropertyChange what_changed;
|
|
|
|
|
|
|
|
what_changed = set_values (node);
|
|
|
|
|
2021-08-31 20:36:16 -04:00
|
|
|
node.get_property (X_("launch-style"), _launch_style);
|
|
|
|
node.get_property (X_("follow-action-0"), _follow_action[0]);
|
|
|
|
node.get_property (X_("follow-action-1"), _follow_action[1]);
|
|
|
|
node.get_property (X_("quantization"), _quantization);
|
|
|
|
node.get_property (X_("name"), _name);
|
|
|
|
node.get_property (X_("index"), _index);
|
|
|
|
|
2021-09-01 00:35:14 -04:00
|
|
|
PBD::ID rid;
|
|
|
|
|
|
|
|
node.get_property (X_("region"), rid);
|
|
|
|
|
|
|
|
boost::shared_ptr<Region> r = RegionFactory::region_by_id (rid);
|
|
|
|
|
|
|
|
if (r) {
|
|
|
|
set_region (r);
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
return 0;
|
2021-07-17 15:21:45 -04:00
|
|
|
}
|
2021-08-12 01:09:36 -04:00
|
|
|
|
2021-09-05 12:40:58 -04:00
|
|
|
void
|
|
|
|
Trigger::set_legato (bool yn)
|
|
|
|
{
|
|
|
|
_legato = yn;
|
|
|
|
PropertyChanged (Properties::legato);
|
|
|
|
}
|
|
|
|
|
2021-08-12 01:09:36 -04:00
|
|
|
void
|
|
|
|
Trigger::set_follow_action_probability (int n)
|
|
|
|
{
|
|
|
|
n = std::min (100, n);
|
|
|
|
n = std::max (0, n);
|
|
|
|
|
|
|
|
_follow_action_probability = n;
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
void
|
|
|
|
Trigger::set_quantization (Temporal::BBT_Offset const & q)
|
2021-07-17 15:21:45 -04:00
|
|
|
{
|
2021-08-08 21:08:16 -04:00
|
|
|
_quantization = q;
|
2021-08-11 20:25:46 -04:00
|
|
|
set_usable_length ();
|
2021-09-29 20:00:55 -04:00
|
|
|
PropertyChanged (Properties::quantization);
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
2021-07-17 15:21:45 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
void
|
|
|
|
Trigger::set_region_internal (boost::shared_ptr<Region> r)
|
|
|
|
{
|
|
|
|
_region = r;
|
2021-07-17 15:21:45 -04:00
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
Temporal::BBT_Offset
|
|
|
|
Trigger::quantization () const
|
2021-07-17 15:21:45 -04:00
|
|
|
{
|
2021-08-08 21:08:16 -04:00
|
|
|
return _quantization;
|
2021-07-17 15:21:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2021-09-04 12:37:36 -04:00
|
|
|
Trigger::stop (int next)
|
2021-07-17 15:21:45 -04:00
|
|
|
{
|
2021-08-08 21:08:16 -04:00
|
|
|
request_state (Stopped);
|
2021-07-17 15:21:45 -04:00
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
void
|
2021-09-04 12:37:36 -04:00
|
|
|
Trigger::request_state (State s)
|
2021-08-05 17:03:28 -04:00
|
|
|
{
|
2021-09-04 12:37:36 -04:00
|
|
|
_requested_state.store (s);
|
2021-08-05 17:03:28 -04:00
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
void
|
2021-09-04 12:37:36 -04:00
|
|
|
Trigger::startup()
|
2021-07-17 15:21:45 -04:00
|
|
|
{
|
2021-09-04 12:37:36 -04:00
|
|
|
_state = WaitingToStart;
|
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
|
|
|
}
|
|
|
|
|
2021-09-05 12:40:58 -04:00
|
|
|
void
|
|
|
|
Trigger::jump_start()
|
|
|
|
{
|
|
|
|
/* this is used when we start a new trigger in legato mode. We do not
|
|
|
|
wait for quantization.
|
|
|
|
*/
|
|
|
|
_state = Running;
|
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Trigger::jump_stop()
|
|
|
|
{
|
|
|
|
/* this is used when we start a new trigger in legato mode. We do not
|
|
|
|
wait for quantization.
|
|
|
|
*/
|
|
|
|
_state = Stopped;
|
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
|
|
|
}
|
|
|
|
|
2021-07-19 11:57:31 -04:00
|
|
|
void
|
2021-08-08 21:08:16 -04:00
|
|
|
Trigger::process_state_requests ()
|
2021-07-19 11:57:31 -04:00
|
|
|
{
|
2021-08-08 21:08:16 -04:00
|
|
|
State new_state = _requested_state.exchange (None);
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
if (new_state != None && new_state != _state) {
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 requested state %2\n", index(), enum_2_string (new_state)));
|
|
|
|
|
|
|
|
switch (new_state) {
|
|
|
|
case Stopped:
|
2021-09-04 12:37:36 -04:00
|
|
|
if (_state != WaitingToStop) {
|
2021-09-05 12:40:58 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 %2 => %3\n", index(), enum_2_string (_state), enum_2_string (WaitingToStop)));
|
2021-09-04 12:37:36 -04:00
|
|
|
_state = WaitingToStop;
|
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
|
|
|
}
|
2021-08-10 22:35:39 -04:00
|
|
|
break;
|
|
|
|
case Running:
|
2021-09-05 01:19:47 -04:00
|
|
|
_box.queue_explict (this);
|
2021-08-10 22:35:39 -04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2021-07-19 11:57:31 -04:00
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
/* now check bangs/unbangs */
|
2021-07-27 22:39:40 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
int x;
|
2021-07-27 22:39:40 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
while ((x = _bang.load ())) {
|
2021-08-05 17:03:28 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
_bang.fetch_sub (1);
|
2021-08-05 17:03:28 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 handling bang with state = %2\n", index(), enum_2_string (_state)));
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
switch (_state) {
|
|
|
|
case None:
|
|
|
|
abort ();
|
|
|
|
break;
|
2021-08-05 17:03:28 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
case Running:
|
|
|
|
switch (launch_style()) {
|
2021-08-12 00:16:16 -04:00
|
|
|
case OneShot:
|
2021-09-05 12:40:58 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 oneshot %2 => %3\n", index(), enum_2_string (Running), enum_2_string (WaitingForRetrigger)));
|
2021-08-10 22:35:39 -04:00
|
|
|
_state = WaitingForRetrigger;
|
2021-08-31 13:53:24 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
2021-08-08 21:08:16 -04:00
|
|
|
break;
|
|
|
|
case Gate:
|
|
|
|
case Toggle:
|
|
|
|
case Repeat:
|
2021-09-05 12:40:58 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 %2 gate/toggle/repeat => %3\n", index(), enum_2_string (Running), enum_2_string (WaitingToStop)));
|
2021-08-10 22:35:39 -04:00
|
|
|
_state = WaitingToStop;
|
2021-09-05 12:40:58 -04:00
|
|
|
_box.clear_implicit ();
|
2021-08-31 13:53:24 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
break;
|
2021-08-05 17:03:28 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
case Stopped:
|
2021-09-05 12:40:58 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 %2 stopped => %3\n", index(), enum_2_string (Stopped), enum_2_string (WaitingToStart)));
|
2021-09-05 01:19:47 -04:00
|
|
|
_box.queue_explict (this);
|
2021-08-08 21:08:16 -04:00
|
|
|
break;
|
2021-08-05 17:03:28 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
case WaitingToStart:
|
|
|
|
case WaitingToStop:
|
2021-08-10 22:35:39 -04:00
|
|
|
case WaitingForRetrigger:
|
2021-08-08 21:08:16 -04:00
|
|
|
case Stopping:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-07-17 15:21:45 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
while ((x = _unbang.load ())) {
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
_unbang.fetch_sub (1);
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-12 14:42:29 -04:00
|
|
|
if (_launch_style == Gate || _launch_style == Repeat) {
|
2021-08-11 16:18:34 -04:00
|
|
|
switch (_state) {
|
|
|
|
case Running:
|
|
|
|
_state = WaitingToStop;
|
2021-08-31 13:53:24 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
2021-08-11 16:18:34 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 unbanged, now in WaitingToStop\n", index()));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* didn't even get started */
|
|
|
|
_state = Stopped;
|
2021-08-31 13:53:24 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
2021-08-11 16:18:34 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 unbanged, never started, now stopped\n", index()));
|
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
Trigger::RunType
|
|
|
|
Trigger::maybe_compute_next_transition (Temporal::Beats const & start, Temporal::Beats const & end)
|
2021-08-08 21:08:16 -04:00
|
|
|
{
|
2021-08-10 22:35:39 -04:00
|
|
|
/* In these states, we are not waiting for a transition */
|
|
|
|
|
|
|
|
switch (_state) {
|
|
|
|
case Stopped:
|
|
|
|
return RunNone;
|
|
|
|
case Running:
|
|
|
|
return RunAll;
|
|
|
|
case Stopping:
|
|
|
|
return RunAll;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
timepos_t ev_time (Temporal::BeatTime);
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
if (_quantization.bars == 0) {
|
|
|
|
ev_time = timepos_t (start.snap_to (Temporal::Beats (_quantization.beats, _quantization.ticks)));
|
2021-08-10 22:35:39 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 quantized with %5 start at %2, sb %3 eb %4\n", index(), ev_time.beats(), start, end, _quantization));
|
2021-08-08 21:08:16 -04:00
|
|
|
} else {
|
|
|
|
/* XXX not yet handled */
|
|
|
|
}
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
if (ev_time.beats() >= start && ev_time < end) {
|
2021-08-10 22:35:39 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
bang_samples = ev_time.samples();
|
|
|
|
bang_beats = ev_time.beats ();
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
if (_state == WaitingToStop) {
|
|
|
|
_state = Stopping;
|
2021-08-31 13:53:24 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
2021-08-10 22:35:39 -04:00
|
|
|
return RunEnd;
|
|
|
|
} else if (_state == WaitingToStart) {
|
|
|
|
retrigger ();
|
|
|
|
_state = Running;
|
2021-09-04 12:37:36 -04:00
|
|
|
_box.prepare_next (_index);
|
2021-08-31 13:53:24 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
2021-08-10 22:35:39 -04:00
|
|
|
return RunStart;
|
|
|
|
} else if (_state == WaitingForRetrigger) {
|
2021-08-08 21:08:16 -04:00
|
|
|
retrigger ();
|
|
|
|
_state = Running;
|
2021-09-04 12:37:36 -04:00
|
|
|
_box.prepare_next (_index);
|
2021-08-31 13:53:24 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
2021-08-10 22:35:39 -04:00
|
|
|
return RunAll;
|
|
|
|
}
|
|
|
|
} else {
|
2021-08-11 16:18:34 -04:00
|
|
|
if (_state == WaitingForRetrigger || _state == WaitingToStop) {
|
2021-08-10 22:35:39 -04:00
|
|
|
/* retrigger time has not been reached, just continue
|
|
|
|
to play normally until then.
|
|
|
|
*/
|
|
|
|
return RunAll;
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
}
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
return RunNone;
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
/*--------------------*/
|
2021-07-17 21:01:10 -04:00
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
AudioTrigger::AudioTrigger (uint64_t n, TriggerBox& b)
|
2021-08-08 21:08:16 -04:00
|
|
|
: Trigger (n, b)
|
|
|
|
, data (0)
|
2021-08-10 22:35:39 -04:00
|
|
|
, read_index (0)
|
2021-08-08 21:08:16 -04:00
|
|
|
, data_length (0)
|
2021-08-13 00:59:04 -04:00
|
|
|
, _start_offset (0)
|
2021-08-31 13:53:24 -04:00
|
|
|
, _legato_offset (0)
|
2021-08-11 20:14:27 -04:00
|
|
|
, usable_length (0)
|
2021-08-13 00:59:04 -04:00
|
|
|
, last_sample (0)
|
2021-08-08 21:08:16 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioTrigger::~AudioTrigger ()
|
|
|
|
{
|
|
|
|
for (std::vector<Sample*>::iterator d = data.begin(); d != data.end(); ++d) {
|
|
|
|
delete *d;
|
2021-07-17 15:21:45 -04:00
|
|
|
}
|
2021-07-20 00:37:17 -04:00
|
|
|
}
|
2021-07-17 15:21:45 -04:00
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
void
|
|
|
|
AudioTrigger::startup ()
|
|
|
|
{
|
|
|
|
Trigger::startup ();
|
|
|
|
retrigger ();
|
|
|
|
}
|
|
|
|
|
2021-09-05 12:40:58 -04:00
|
|
|
void
|
|
|
|
AudioTrigger::jump_start ()
|
|
|
|
{
|
|
|
|
Trigger::jump_start ();
|
|
|
|
retrigger ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
AudioTrigger::jump_stop ()
|
|
|
|
{
|
|
|
|
Trigger::jump_stop ();
|
|
|
|
retrigger ();
|
|
|
|
}
|
|
|
|
|
2021-10-04 00:44:03 -04:00
|
|
|
double
|
|
|
|
AudioTrigger::position_as_fraction () const
|
|
|
|
{
|
|
|
|
if (!active()) {
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return read_index / (double) usable_length;
|
|
|
|
}
|
|
|
|
|
2021-08-31 18:46:19 -04:00
|
|
|
XMLNode&
|
|
|
|
AudioTrigger::get_state (void)
|
|
|
|
{
|
|
|
|
XMLNode& node (Trigger::get_state());
|
|
|
|
|
|
|
|
node.set_property (X_("start"), timepos_t (_start_offset));
|
|
|
|
node.set_property (X_("length"), timepos_t (usable_length));
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2021-08-31 20:36:16 -04:00
|
|
|
AudioTrigger::set_state (const XMLNode& node, int version)
|
2021-08-31 18:46:19 -04:00
|
|
|
{
|
2021-08-31 20:36:16 -04:00
|
|
|
timepos_t t;
|
|
|
|
|
2021-09-01 00:35:14 -04:00
|
|
|
if (!Trigger::set_state (node, version)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-08-31 20:36:16 -04:00
|
|
|
node.get_property (X_("start"), t);
|
|
|
|
_start_offset = t.samples();
|
|
|
|
|
|
|
|
node.get_property (X_("length"), t);
|
|
|
|
usable_length = t.samples();
|
|
|
|
last_sample = _start_offset + usable_length;
|
|
|
|
|
2021-08-31 18:46:19 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-07-20 00:37:17 -04:00
|
|
|
void
|
2021-08-31 13:53:24 -04:00
|
|
|
AudioTrigger::set_start (timepos_t const & s)
|
2021-08-13 00:59:04 -04:00
|
|
|
{
|
|
|
|
_start_offset = s.samples ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2021-08-31 13:53:24 -04:00
|
|
|
AudioTrigger::set_end (timepos_t const & e)
|
2021-08-13 00:59:04 -04:00
|
|
|
{
|
2021-10-06 17:20:52 -04:00
|
|
|
assert (!data.empty());
|
2021-10-05 18:01:52 -04:00
|
|
|
set_length (timecnt_t (e.samples() - _start_offset, timepos_t (_start_offset)));
|
2021-08-13 00:59:04 -04:00
|
|
|
}
|
|
|
|
|
2021-08-31 13:53:24 -04:00
|
|
|
void
|
|
|
|
AudioTrigger::set_legato_offset (timepos_t const & offset)
|
|
|
|
{
|
|
|
|
_legato_offset = offset.samples();
|
|
|
|
}
|
|
|
|
|
|
|
|
timepos_t
|
|
|
|
AudioTrigger::current_pos() const
|
|
|
|
{
|
|
|
|
return timepos_t (read_index);
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:59:04 -04:00
|
|
|
timepos_t
|
|
|
|
AudioTrigger::end() const
|
|
|
|
{
|
|
|
|
return timepos_t (_start_offset + usable_length);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2021-10-05 18:01:52 -04:00
|
|
|
AudioTrigger::set_length (timecnt_t const & newlen)
|
2021-07-20 00:37:17 -04:00
|
|
|
{
|
2021-08-08 21:08:16 -04:00
|
|
|
using namespace RubberBand;
|
|
|
|
using namespace Temporal;
|
2021-08-06 23:26:50 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
if (!_region) {
|
2021-07-20 00:37:17 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
boost::shared_ptr<AudioRegion> ar (boost::dynamic_pointer_cast<AudioRegion> (_region));
|
|
|
|
|
2021-10-05 18:01:52 -04:00
|
|
|
if (newlen == _region->length()) {
|
2021-08-08 21:08:16 -04:00
|
|
|
/* no stretch required */
|
2021-07-19 11:57:31 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
/* offline stretch */
|
2021-08-05 17:03:28 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
/* study */
|
2021-08-05 17:03:28 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
const uint32_t nchans = ar->n_channels();
|
2021-08-05 17:03:28 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
RubberBandStretcher::Options options = RubberBandStretcher::Option (RubberBandStretcher::OptionProcessOffline|RubberBandStretcher::OptionStretchPrecise);
|
|
|
|
RubberBandStretcher stretcher (_box.session().sample_rate(), nchans, options, 1.0, 1.0);
|
2021-08-05 17:03:28 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
/* Compute stretch ratio */
|
2021-07-20 00:37:17 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
if (newlen.time_domain() == AudioTime) {
|
2021-10-05 23:09:16 -04:00
|
|
|
_stretch = (double) newlen.samples() / data_length;
|
2021-10-06 17:20:52 -04:00
|
|
|
cerr << "gonna stretch, ratio is " << _stretch << " from " << newlen.samples() << " vs. " << data_length << endl;
|
2021-08-08 21:08:16 -04:00
|
|
|
} else {
|
|
|
|
/* XXX what to use for position ??? */
|
2021-08-13 00:59:04 -04:00
|
|
|
timecnt_t l (newlen, timepos_t (AudioTime));
|
|
|
|
const timecnt_t dur = TempoMap::use()->convert_duration (l, timepos_t (0), AudioTime);
|
2021-10-05 23:09:16 -04:00
|
|
|
_stretch = (double) dur.samples() / data_length;
|
2021-07-20 00:37:17 -04:00
|
|
|
}
|
|
|
|
|
2021-10-05 23:09:16 -04:00
|
|
|
stretcher.setTimeRatio (_stretch);
|
2021-10-05 18:01:52 -04:00
|
|
|
|
2021-10-05 23:09:16 -04:00
|
|
|
const samplecnt_t expected_length = ceil (data_length * _stretch) + 16; /* extra space for safety */
|
2021-08-08 21:08:16 -04:00
|
|
|
std::vector<Sample*> stretched;
|
2021-08-06 15:03:28 -04:00
|
|
|
|
|
|
|
for (uint32_t n = 0; n < nchans; ++n) {
|
2021-08-06 23:26:50 -04:00
|
|
|
stretched.push_back (new Sample[expected_length]);
|
2021-08-06 15:03:28 -04:00
|
|
|
}
|
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
/* RB expects array-of-ptr-to-Sample, so set one up */
|
|
|
|
|
2021-08-29 10:40:59 -04:00
|
|
|
std::vector<Sample*> raw(nchans);
|
|
|
|
std::vector<Sample*> results(nchans);
|
2021-08-06 23:26:50 -04:00
|
|
|
|
2021-08-06 15:03:28 -04:00
|
|
|
/* study, then process */
|
|
|
|
|
2021-08-07 17:08:26 -04:00
|
|
|
const samplecnt_t block_size = 16384;
|
2021-08-06 23:26:50 -04:00
|
|
|
samplecnt_t read = 0;
|
|
|
|
|
|
|
|
stretcher.setDebugLevel (0);
|
|
|
|
stretcher.setMaxProcessSize (block_size);
|
2021-08-06 15:03:28 -04:00
|
|
|
stretcher.setExpectedInputDuration (data_length);
|
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
while (read < data_length) {
|
2021-08-06 15:03:28 -04:00
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
for (uint32_t n = 0; n < nchans; ++n) {
|
|
|
|
raw[n] = data[n] + read;
|
|
|
|
}
|
2021-08-06 15:03:28 -04:00
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
samplecnt_t to_read = std::min (block_size, data_length - read);
|
|
|
|
read += to_read;
|
2021-08-06 15:03:28 -04:00
|
|
|
|
2021-08-29 10:40:59 -04:00
|
|
|
stretcher.study (&raw[0], to_read, (read >= data_length));
|
2021-08-06 15:03:28 -04:00
|
|
|
}
|
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
read = 0;
|
2021-08-06 15:03:28 -04:00
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
samplecnt_t processed = 0;
|
|
|
|
samplecnt_t avail;
|
|
|
|
|
|
|
|
while (read < data_length) {
|
|
|
|
|
|
|
|
for (uint32_t n = 0; n < nchans; ++n) {
|
|
|
|
raw[n] = data[n] + read;
|
|
|
|
}
|
|
|
|
|
|
|
|
samplecnt_t to_read = std::min (block_size, data_length - read);
|
|
|
|
read += to_read;
|
|
|
|
|
2021-08-29 10:40:59 -04:00
|
|
|
stretcher.process (&raw[0], to_read, (read >= data_length));
|
2021-08-06 23:26:50 -04:00
|
|
|
|
|
|
|
while ((avail = stretcher.available()) > 0) {
|
|
|
|
|
|
|
|
for (uint32_t n = 0; n < nchans; ++n) {
|
|
|
|
results[n] = stretched[n] + processed;
|
|
|
|
}
|
|
|
|
|
2021-08-29 10:40:59 -04:00
|
|
|
processed += stretcher.retrieve (&results[0], avail);
|
2021-08-06 23:26:50 -04:00
|
|
|
}
|
2021-08-06 15:03:28 -04:00
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
/* collect final chunk of data, possible delayed by thread activity in stretcher */
|
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
while ((avail = stretcher.available()) >= 0) {
|
2021-08-06 15:03:28 -04:00
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
if (avail == 0) {
|
|
|
|
Glib::usleep (10000);
|
|
|
|
continue;
|
|
|
|
}
|
2021-08-06 15:03:28 -04:00
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
for (uint32_t n = 0; n < nchans; ++n) {
|
|
|
|
results[n] = stretched[n] + processed;
|
|
|
|
}
|
2021-08-06 15:03:28 -04:00
|
|
|
|
2021-08-29 10:40:59 -04:00
|
|
|
processed += stretcher.retrieve (&results[0], avail);
|
2021-08-06 23:26:50 -04:00
|
|
|
}
|
2021-08-06 15:03:28 -04:00
|
|
|
|
2021-08-06 23:26:50 -04:00
|
|
|
/* allocate new data buffers */
|
|
|
|
|
|
|
|
drop_data ();
|
|
|
|
data = stretched;
|
|
|
|
data_length = processed;
|
2021-08-11 20:14:27 -04:00
|
|
|
if (!usable_length || usable_length > data_length) {
|
|
|
|
usable_length = data_length;
|
2021-08-13 00:59:04 -04:00
|
|
|
last_sample = _start_offset + usable_length;
|
2021-08-11 20:14:27 -04:00
|
|
|
}
|
2021-08-06 15:03:28 -04:00
|
|
|
}
|
|
|
|
|
2021-08-11 20:25:46 -04:00
|
|
|
void
|
|
|
|
AudioTrigger::set_usable_length ()
|
|
|
|
{
|
|
|
|
if (!_region) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (_launch_style) {
|
|
|
|
case Repeat:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
usable_length = data_length;
|
2021-08-13 00:59:04 -04:00
|
|
|
last_sample = _start_offset + usable_length;
|
2021-08-11 20:25:46 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_quantization == Temporal::BBT_Offset ()) {
|
|
|
|
usable_length = data_length;
|
2021-08-13 00:59:04 -04:00
|
|
|
last_sample = _start_offset + usable_length;
|
2021-08-11 20:25:46 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* XXX MUST HANDLE BAR-LEVEL QUANTIZATION */
|
|
|
|
|
|
|
|
timecnt_t len (Temporal::Beats (_quantization.beats, _quantization.ticks), timepos_t (Temporal::Beats()));
|
|
|
|
usable_length = len.samples();
|
2021-08-13 00:59:04 -04:00
|
|
|
last_sample = _start_offset + usable_length;
|
2021-08-11 20:25:46 -04:00
|
|
|
}
|
|
|
|
|
2021-08-13 00:59:04 -04:00
|
|
|
timepos_t
|
2021-08-06 15:03:28 -04:00
|
|
|
AudioTrigger::current_length() const
|
|
|
|
{
|
|
|
|
if (_region) {
|
2021-08-13 00:59:04 -04:00
|
|
|
return timepos_t (data_length);
|
2021-08-06 15:03:28 -04:00
|
|
|
}
|
2021-08-13 00:59:04 -04:00
|
|
|
return timepos_t (Temporal::BeatTime);
|
2021-08-06 15:03:28 -04:00
|
|
|
}
|
|
|
|
|
2021-08-13 00:59:04 -04:00
|
|
|
timepos_t
|
2021-08-06 15:03:28 -04:00
|
|
|
AudioTrigger::natural_length() const
|
|
|
|
{
|
|
|
|
if (_region) {
|
2021-08-13 00:59:04 -04:00
|
|
|
return timepos_t::from_superclock (_region->length().magnitude());
|
2021-08-06 15:03:28 -04:00
|
|
|
}
|
2021-08-13 00:59:04 -04:00
|
|
|
return timepos_t (Temporal::BeatTime);
|
2021-08-06 15:03:28 -04:00
|
|
|
}
|
|
|
|
|
2021-07-19 11:57:31 -04:00
|
|
|
int
|
|
|
|
AudioTrigger::set_region (boost::shared_ptr<Region> r)
|
|
|
|
{
|
2021-10-05 18:01:52 -04:00
|
|
|
using namespace Temporal;
|
|
|
|
|
2021-07-19 11:57:31 -04:00
|
|
|
boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (r);
|
|
|
|
|
|
|
|
if (!ar) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_region_internal (r);
|
|
|
|
|
2021-10-06 17:20:52 -04:00
|
|
|
/* load raw data */
|
|
|
|
|
|
|
|
load_data (ar);
|
|
|
|
|
2021-10-05 23:09:16 -04:00
|
|
|
/* now potentially stretch it to match our tempo.
|
|
|
|
*
|
|
|
|
* We do not handle tempo changes at present, and should probably issue
|
|
|
|
* a warming about this.
|
|
|
|
*/
|
2021-08-06 15:03:28 -04:00
|
|
|
|
2021-10-05 18:01:52 -04:00
|
|
|
TempoMap::SharedPtr tm (TempoMap::use());
|
2021-10-06 17:20:52 -04:00
|
|
|
TempoMetric const & metric (tm->metric_at (timepos_t (AudioTime)));
|
|
|
|
|
|
|
|
double barcnt;
|
|
|
|
|
|
|
|
{
|
|
|
|
breakfastquay::MiniBPM mbpm (_box.session().sample_rate());
|
|
|
|
|
|
|
|
mbpm.setBPMRange (metric.tempo().quarter_notes_per_minute () * 0.75, metric.tempo().quarter_notes_per_minute() * 1.5);
|
|
|
|
double bpm = mbpm.estimateTempoOfSamples (data[0], data_length);
|
|
|
|
const double seconds = (double) data_length / _box.session().sample_rate();
|
|
|
|
const double quarters = (seconds / 60.) * bpm;
|
|
|
|
barcnt = quarters / metric.meter().divisions_per_bar();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* use initial tempo in map (assumed for now to be the only one */
|
|
|
|
|
|
|
|
samplecnt_t one_bar = tm->bbt_duration_at (timepos_t (AudioTime), BBT_Offset (1, 0, 0)).samples();
|
2021-10-05 18:01:52 -04:00
|
|
|
|
2021-10-06 17:20:52 -04:00
|
|
|
set_length (timecnt_t ((samplecnt_t) round (barcnt) * one_bar));
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-05 18:20:37 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::name);
|
|
|
|
|
2021-07-19 11:57:31 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
AudioTrigger::drop_data ()
|
|
|
|
{
|
|
|
|
for (uint32_t n = 0; n < data.size(); ++n) {
|
|
|
|
delete [] data[n];
|
|
|
|
}
|
|
|
|
data.clear ();
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
AudioTrigger::load_data (boost::shared_ptr<AudioRegion> ar)
|
|
|
|
{
|
|
|
|
const uint32_t nchans = ar->n_channels();
|
|
|
|
|
2021-08-06 15:03:28 -04:00
|
|
|
data_length = ar->length_samples();
|
2021-07-19 11:57:31 -04:00
|
|
|
|
2021-08-11 20:14:27 -04:00
|
|
|
/* if usable length was already set, only adjust it if it is too large */
|
|
|
|
if (!usable_length || usable_length > data_length) {
|
|
|
|
usable_length = data_length;
|
2021-08-13 00:59:04 -04:00
|
|
|
last_sample = _start_offset + usable_length;
|
2021-08-11 20:14:27 -04:00
|
|
|
}
|
|
|
|
|
2021-07-19 11:57:31 -04:00
|
|
|
drop_data ();
|
|
|
|
|
|
|
|
try {
|
|
|
|
for (uint32_t n = 0; n < nchans; ++n) {
|
2021-08-06 15:03:28 -04:00
|
|
|
data.push_back (new Sample[data_length]);
|
|
|
|
ar->read (data[n], 0, data_length, n);
|
2021-07-19 11:57:31 -04:00
|
|
|
}
|
2021-08-31 13:53:24 -04:00
|
|
|
|
|
|
|
set_name (ar->name());
|
|
|
|
|
2021-07-19 11:57:31 -04:00
|
|
|
} catch (...) {
|
|
|
|
drop_data ();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-07-17 15:21:45 -04:00
|
|
|
void
|
2021-07-21 01:04:11 -04:00
|
|
|
AudioTrigger::retrigger ()
|
2021-07-17 15:21:45 -04:00
|
|
|
{
|
2021-08-31 13:53:24 -04:00
|
|
|
read_index = _start_offset + _legato_offset;
|
2021-09-05 12:40:58 -04:00
|
|
|
_legato_offset = 0; /* used one time only */
|
2021-09-05 01:19:47 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 retriggered to %2\n", _index, read_index));
|
2021-07-19 11:57:31 -04:00
|
|
|
}
|
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
int
|
|
|
|
AudioTrigger::run (BufferSet& bufs, pframes_t nframes, pframes_t dest_offset, bool first)
|
2021-07-17 15:21:45 -04:00
|
|
|
{
|
2021-08-10 22:35:39 -04:00
|
|
|
boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(_region);
|
2021-08-11 16:18:34 -04:00
|
|
|
const bool long_enough_to_fade = (nframes >= 64);
|
2021-08-07 20:53:49 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
assert (ar);
|
|
|
|
assert (active());
|
2021-07-17 15:21:45 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
while (nframes) {
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-13 00:59:04 -04:00
|
|
|
pframes_t this_read = (pframes_t) std::min ((samplecnt_t) nframes, (last_sample - read_index));
|
2021-07-17 15:21:45 -04:00
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
for (uint64_t chn = 0; chn < ar->n_channels(); ++chn) {
|
2021-08-06 23:26:50 -04:00
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
uint64_t channel = chn % data.size();
|
2021-08-10 22:35:39 -04:00
|
|
|
Sample* src = data[channel] + read_index;
|
|
|
|
AudioBuffer& buf (bufs.get_audio (chn));
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-11 16:18:34 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
if (first) {
|
|
|
|
buf.read_from (src, this_read, dest_offset);
|
|
|
|
} else {
|
|
|
|
buf.accumulate_from (src, this_read, dest_offset);
|
|
|
|
}
|
|
|
|
}
|
2021-08-06 23:26:50 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
read_index += this_read;
|
2021-08-06 23:26:50 -04:00
|
|
|
|
2021-08-13 00:59:04 -04:00
|
|
|
if (read_index >= last_sample) {
|
2021-08-06 23:26:50 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
/* We reached the end */
|
2021-08-07 17:08:26 -04:00
|
|
|
|
2021-09-05 12:40:58 -04:00
|
|
|
if ((_launch_style == Repeat) || (_box.peek_next_trigger() == this)) { /* self repeat */
|
2021-08-10 22:35:39 -04:00
|
|
|
nframes -= this_read;
|
|
|
|
dest_offset += this_read;
|
2021-08-12 14:42:29 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached end, but set to loop, so retrigger\n", index()));
|
2021-08-10 22:35:39 -04:00
|
|
|
retrigger ();
|
|
|
|
/* and go around again */
|
|
|
|
continue;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (this_read < nframes) {
|
2021-08-06 23:26:50 -04:00
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
for (uint64_t chn = 0; chn < ar->n_channels(); ++chn) {
|
|
|
|
uint64_t channel = chn % data.size();
|
2021-08-10 22:35:39 -04:00
|
|
|
AudioBuffer& buf (bufs.get_audio (channel));
|
2021-09-05 01:19:47 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 short fill, ri %2 vs ls %3, do silent fill\n", index(), read_index, last_sample));
|
2021-08-10 22:35:39 -04:00
|
|
|
buf.silence (nframes - this_read, dest_offset + this_read);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_state = Stopped;
|
2021-08-31 13:53:24 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
2021-08-12 14:42:29 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached end, now stopped\n", index()));
|
2021-08-10 22:35:39 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nframes -= this_read;
|
2021-08-07 17:08:26 -04:00
|
|
|
}
|
|
|
|
|
2021-08-11 16:18:34 -04:00
|
|
|
if (_state == Stopping && long_enough_to_fade) {
|
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 was stopping, now stopped\n", index()));
|
|
|
|
_state = Stopped;
|
2021-08-31 13:53:24 -04:00
|
|
|
PropertyChanged (ARDOUR::Properties::running);
|
2021-08-11 16:18:34 -04:00
|
|
|
}
|
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
return 0;
|
2021-07-17 15:21:45 -04:00
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
|
|
|
|
/**************/
|
|
|
|
|
|
|
|
void
|
|
|
|
Trigger::make_property_quarks ()
|
|
|
|
{
|
|
|
|
Properties::muted.property_id = g_quark_from_static_string (X_("running"));
|
|
|
|
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for running = %1\n", Properties::running.property_id));
|
2021-09-28 19:45:35 -04:00
|
|
|
Properties::legato.property_id = g_quark_from_static_string (X_("legato"));
|
|
|
|
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for legato = %1\n", Properties::legato.property_id));
|
|
|
|
Properties::use_follow.property_id = g_quark_from_static_string (X_("use-follow"));
|
|
|
|
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for use-follow = %1\n", Properties::use_follow.property_id));
|
2021-09-29 20:00:55 -04:00
|
|
|
Properties::quantization.property_id = g_quark_from_static_string (X_("quantization"));
|
|
|
|
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for quantization = %1\n", Properties::quantization.property_id));
|
2021-10-01 20:39:06 -04:00
|
|
|
Properties::launch_style.property_id = g_quark_from_static_string (X_("launch-style"));
|
|
|
|
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for quantization = %1\n", Properties::launch_style.property_id));
|
|
|
|
Properties::follow_action0.property_id = g_quark_from_static_string (X_("follow-action-0"));
|
|
|
|
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for follow-action-0 = %1\n", Properties::follow_action0.property_id));
|
|
|
|
Properties::follow_action1.property_id = g_quark_from_static_string (X_("follow-action-1"));
|
|
|
|
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for follow-action-1 = %1\n", Properties::follow_action1.property_id));
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
const uint64_t TriggerBox::default_triggers_per_box = 8;
|
2021-10-05 23:09:16 -04:00
|
|
|
Temporal::BBT_Offset TriggerBox::_assumed_trigger_duration (4, 0, 0);
|
2021-08-31 13:53:24 -04:00
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
TriggerBox::TriggerBox (Session& s, DataType dt)
|
|
|
|
: Processor (s, _("TriggerBox"), Temporal::BeatTime)
|
|
|
|
, _bang_queue (1024)
|
|
|
|
, _unbang_queue (1024)
|
|
|
|
, _data_type (dt)
|
2021-09-04 12:37:36 -04:00
|
|
|
, explicit_queue (64)
|
|
|
|
, implicit_queue (64)
|
2021-09-05 01:19:47 -04:00
|
|
|
, currently_playing (0)
|
|
|
|
, _stop_all (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-09-10 15:12:14 -04:00
|
|
|
for (uint64_t n = 0; n < default_triggers_per_box; ++n) {
|
2021-08-08 21:08:16 -04:00
|
|
|
all_triggers.push_back (new AudioTrigger (n, *this));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (60), 0));
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (61), 1));
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (62), 2));
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (63), 3));
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (64), 4));
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (65), 5));
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (66), 6));
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (67), 7));
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (68), 8));
|
|
|
|
midi_trigger_map.insert (midi_trigger_map.end(), std::make_pair (uint8_t (69), 9));
|
|
|
|
}
|
|
|
|
|
2021-09-05 12:40:58 -04:00
|
|
|
void
|
|
|
|
TriggerBox::clear_implicit ()
|
|
|
|
{
|
|
|
|
implicit_queue.reset ();
|
|
|
|
}
|
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
void
|
|
|
|
TriggerBox::queue_explict (Trigger* t)
|
|
|
|
{
|
2021-09-05 01:19:47 -04:00
|
|
|
assert (t);
|
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("explicit queue %1\n", t->index()));
|
2021-09-04 12:37:36 -04:00
|
|
|
explicit_queue.write (&t, 1);
|
|
|
|
implicit_queue.reset ();
|
2021-09-05 01:19:47 -04:00
|
|
|
|
|
|
|
if (currently_playing) {
|
|
|
|
currently_playing->unbang ();
|
|
|
|
}
|
2021-09-04 12:37:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TriggerBox::queue_implicit (Trigger* t)
|
|
|
|
{
|
2021-09-05 01:19:47 -04:00
|
|
|
assert (t);
|
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
if (explicit_queue.read_space() == 0) {
|
2021-09-05 01:19:47 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("implicit queue %1\n", t->index()));
|
2021-09-04 12:37:36 -04:00
|
|
|
implicit_queue.write (&t, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
Trigger*
|
2021-09-05 12:40:58 -04:00
|
|
|
TriggerBox::peek_next_trigger ()
|
2021-09-05 01:19:47 -04:00
|
|
|
{
|
2021-09-05 12:40:58 -04:00
|
|
|
/* allows us to check if there's a next trigger queued, without
|
|
|
|
* actually reading it from either of the queues.
|
|
|
|
*/
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
RingBuffer<Trigger*>::rw_vector rwv;
|
|
|
|
|
|
|
|
explicit_queue.get_read_vector (&rwv);
|
|
|
|
if (rwv.len[0] > 0) {
|
|
|
|
return *(rwv.buf[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
implicit_queue.get_read_vector (&rwv);
|
|
|
|
if (rwv.len[0] > 0) {
|
|
|
|
return *(rwv.buf[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
Trigger*
|
|
|
|
TriggerBox::get_next_trigger ()
|
|
|
|
{
|
|
|
|
Trigger* r;
|
|
|
|
|
|
|
|
if (explicit_queue.read (&r, 1) == 1) {
|
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("next trigger from explicit queue = %1\n", r->index()));
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (implicit_queue.read (&r, 1) == 1) {
|
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("next trigger from implicit queue = %1\n", r->index()));
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
int
|
2021-09-10 15:12:14 -04:00
|
|
|
TriggerBox::set_from_path (uint64_t slot, std::string const & path)
|
2021-08-08 21:08:16 -04:00
|
|
|
{
|
|
|
|
assert (slot < all_triggers.size());
|
|
|
|
|
|
|
|
try {
|
|
|
|
SoundFileInfo info;
|
|
|
|
string errmsg;
|
|
|
|
|
|
|
|
if (!SndFileSource::get_soundfile_info (path, info, errmsg)) {
|
|
|
|
error << string_compose (_("Cannot get info from audio file %1 (%2)"), path, errmsg) << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
SourceList src_list;
|
|
|
|
|
|
|
|
for (uint16_t n = 0; n < info.channels; ++n) {
|
2021-09-01 00:35:14 -04:00
|
|
|
boost::shared_ptr<Source> source (SourceFactory::createExternal (DataType::AUDIO, _session, path, n, Source::Flag (0), true));
|
|
|
|
if (!source) {
|
|
|
|
error << string_compose (_("Cannot create source from %1"), path) << endmsg;
|
|
|
|
src_list.clear ();
|
|
|
|
return -1;
|
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
src_list.push_back (source);
|
|
|
|
}
|
|
|
|
|
|
|
|
PropertyList plist;
|
|
|
|
|
|
|
|
plist.add (Properties::start, 0);
|
|
|
|
plist.add (Properties::length, src_list.front()->length ());
|
|
|
|
plist.add (Properties::name, basename_nosuffix (path));
|
|
|
|
plist.add (Properties::layer, 0);
|
|
|
|
plist.add (Properties::layering_index, 0);
|
|
|
|
|
2021-09-01 00:35:14 -04:00
|
|
|
boost::shared_ptr<Region> the_region (RegionFactory::create (src_list, plist, true));
|
2021-08-08 21:08:16 -04:00
|
|
|
|
|
|
|
all_triggers[slot]->set_region (the_region);
|
|
|
|
|
|
|
|
/* XXX catch region going away */
|
|
|
|
|
|
|
|
} catch (std::exception& e) {
|
|
|
|
cerr << "loading sample from " << path << " failed: " << e.what() << endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
TriggerBox::~TriggerBox ()
|
|
|
|
{
|
|
|
|
drop_triggers ();
|
|
|
|
}
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
void
|
|
|
|
TriggerBox::request_stop_all ()
|
|
|
|
{
|
|
|
|
_stop_all = true;
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
void
|
|
|
|
TriggerBox::stop_all ()
|
|
|
|
{
|
|
|
|
/* XXX needs to be done with mutex or via thread-safe queue */
|
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
for (uint64_t n = 0; n < all_triggers.size(); ++n) {
|
2021-09-04 12:37:36 -04:00
|
|
|
all_triggers[n]->stop (-1);
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
2021-09-05 01:19:47 -04:00
|
|
|
|
|
|
|
implicit_queue.reset ();
|
|
|
|
explicit_queue.reset ();
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TriggerBox::drop_triggers ()
|
|
|
|
{
|
|
|
|
Glib::Threads::RWLock::WriterLock lm (trigger_lock);
|
|
|
|
|
|
|
|
for (Triggers::iterator t = all_triggers.begin(); t != all_triggers.end(); ++t) {
|
|
|
|
if (*t) {
|
|
|
|
delete *t;
|
|
|
|
(*t) = 0;
|
|
|
|
}
|
|
|
|
}
|
2021-08-31 21:03:32 -04:00
|
|
|
|
|
|
|
all_triggers.clear ();
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Trigger*
|
|
|
|
TriggerBox::trigger (Triggers::size_type n)
|
|
|
|
{
|
|
|
|
Glib::Threads::RWLock::ReaderLock lm (trigger_lock);
|
|
|
|
|
|
|
|
if (n >= all_triggers.size()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return all_triggers[n];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TriggerBox::can_support_io_configuration (const ChanCount& in, ChanCount& out)
|
|
|
|
{
|
|
|
|
if (in.get(DataType::MIDI) < 1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
out = ChanCount::max (out, ChanCount (DataType::AUDIO, 2));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TriggerBox::configure_io (ChanCount in, ChanCount out)
|
|
|
|
{
|
|
|
|
return Processor::configure_io (in, out);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TriggerBox::add_trigger (Trigger* trigger)
|
|
|
|
{
|
|
|
|
Glib::Threads::RWLock::WriterLock lm (trigger_lock);
|
|
|
|
all_triggers.push_back (trigger);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TriggerBox::process_midi_trigger_requests (BufferSet& bufs)
|
|
|
|
{
|
|
|
|
/* check MIDI port input buffers for triggers */
|
|
|
|
|
|
|
|
for (BufferSet::midi_iterator mi = bufs.midi_begin(); mi != bufs.midi_end(); ++mi) {
|
|
|
|
MidiBuffer& mb (*mi);
|
|
|
|
|
|
|
|
for (MidiBuffer::iterator ev = mb.begin(); ev != mb.end(); ++ev) {
|
|
|
|
|
|
|
|
if (!(*ev).is_note()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
MidiTriggerMap::iterator mt = midi_trigger_map.find ((*ev).note());
|
|
|
|
Trigger* t = 0;
|
|
|
|
|
|
|
|
if (mt != midi_trigger_map.end()) {
|
|
|
|
|
|
|
|
assert (mt->second < all_triggers.size());
|
|
|
|
|
|
|
|
t = all_triggers[mt->second];
|
|
|
|
|
|
|
|
if (!t) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((*ev).is_note_on()) {
|
|
|
|
|
|
|
|
t->bang ();
|
|
|
|
|
|
|
|
} else if ((*ev).is_note_off()) {
|
|
|
|
|
|
|
|
t->unbang ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool result_required)
|
|
|
|
{
|
|
|
|
if (start_sample < 0) {
|
|
|
|
/* we can't do anything under these conditions (related to
|
|
|
|
latency compensation
|
|
|
|
*/
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
process_midi_trigger_requests (bufs);
|
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
|
|
|
|
/* now let each trigger handle any state changes */
|
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
std::vector<uint64_t> to_run;
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
for (uint64_t n = 0; n < all_triggers.size(); ++n) {
|
2021-08-08 21:08:16 -04:00
|
|
|
all_triggers[n]->process_state_requests ();
|
2021-09-05 01:19:47 -04:00
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-09-05 12:40:58 -04:00
|
|
|
Trigger* nxt = 0;
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
if (!currently_playing) {
|
|
|
|
if ((currently_playing = get_next_trigger ()) != 0) {
|
|
|
|
currently_playing->startup ();
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
if (!currently_playing) {
|
2021-08-08 21:08:16 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* transport must be active for triggers */
|
|
|
|
|
|
|
|
if (!_session.transport_state_rolling()) {
|
|
|
|
_session.start_transport_from_processor ();
|
|
|
|
}
|
|
|
|
|
|
|
|
timepos_t start (start_sample);
|
|
|
|
timepos_t end (end_sample);
|
|
|
|
Temporal::Beats start_beats (start.beats());
|
|
|
|
Temporal::Beats end_beats (end.beats());
|
|
|
|
Temporal::TempoMap::SharedPtr tmap (Temporal::TempoMap::use());
|
2021-09-10 15:12:14 -04:00
|
|
|
uint64_t max_chans = 0;
|
2021-08-08 21:08:16 -04:00
|
|
|
bool first = false;
|
|
|
|
|
2021-09-05 12:40:58 -04:00
|
|
|
/* see if there's another trigger explicitly queued that has legato set. */
|
|
|
|
|
|
|
|
RingBuffer<Trigger*>::rw_vector rwv;
|
|
|
|
explicit_queue.get_read_vector (&rwv);
|
|
|
|
|
|
|
|
if (rwv.len[0] > 0) {
|
|
|
|
|
|
|
|
/* actually fetch it (guaranteed to pull from the explicit queue */
|
|
|
|
|
|
|
|
nxt = get_next_trigger ();
|
|
|
|
|
|
|
|
/* if user triggered same clip, with legato set, then there is
|
|
|
|
* nothing to do
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (nxt != currently_playing) {
|
|
|
|
|
|
|
|
if (nxt->legato()) {
|
|
|
|
/* We want to start this trigger immediately, without
|
|
|
|
* waiting for quantization points, and it should start
|
|
|
|
* playing at the same internal offset as the current
|
|
|
|
* trigger.
|
|
|
|
*/
|
|
|
|
|
|
|
|
nxt->set_legato_offset (currently_playing->current_pos());
|
|
|
|
nxt->jump_start ();
|
|
|
|
currently_playing->jump_stop ();
|
|
|
|
prepare_next (nxt->index());
|
|
|
|
/* and switch */
|
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 => %2 switched to in legato mode\n", currently_playing->index(), nxt->index()));
|
|
|
|
currently_playing = nxt;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_stop_all) {
|
|
|
|
stop_all ();
|
|
|
|
_stop_all = false;
|
|
|
|
}
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
while (currently_playing) {
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
assert (currently_playing->state() >= Trigger::WaitingToStart);
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
Trigger::RunType rt;
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
switch (currently_playing->state()) {
|
2021-08-08 21:08:16 -04:00
|
|
|
case Trigger::WaitingToStop:
|
|
|
|
case Trigger::WaitingToStart:
|
2021-08-10 22:35:39 -04:00
|
|
|
case Trigger::WaitingForRetrigger:
|
2021-09-05 01:19:47 -04:00
|
|
|
rt = currently_playing->maybe_compute_next_transition (start_beats, end_beats);
|
2021-08-08 21:08:16 -04:00
|
|
|
break;
|
2021-08-10 22:35:39 -04:00
|
|
|
default:
|
|
|
|
rt = Trigger::RunAll;
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
if (rt == Trigger::RunNone) {
|
|
|
|
/* nothing to do at this time, still waiting to start */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::shared_ptr<Region> r = currently_playing->region();
|
|
|
|
|
|
|
|
sampleoffset_t dest_offset;
|
|
|
|
pframes_t trigger_samples;
|
|
|
|
|
|
|
|
const bool was_waiting_to_start = (currently_playing->state() == Trigger::WaitingToStart);
|
2021-08-10 22:35:39 -04:00
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
if (rt == Trigger::RunEnd) {
|
2021-08-08 21:08:16 -04:00
|
|
|
|
|
|
|
/* trigger will reach it's end somewhere within this
|
|
|
|
* process cycle, so compute the number of samples it
|
|
|
|
* should generate.
|
|
|
|
*/
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
trigger_samples = nframes - (currently_playing->bang_samples - start_sample);
|
2021-08-08 21:08:16 -04:00
|
|
|
dest_offset = 0;
|
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
} else if (rt == Trigger::RunStart) {
|
2021-08-08 21:08:16 -04:00
|
|
|
|
|
|
|
/* trigger will start somewhere within this process
|
|
|
|
* cycle. Compute the sample offset where any audio
|
|
|
|
* should end up, and the number of samples it should generate.
|
|
|
|
*/
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
dest_offset = std::max (samplepos_t (0), currently_playing->bang_samples - start_sample);
|
2021-08-08 21:08:16 -04:00
|
|
|
trigger_samples = nframes - dest_offset;
|
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
} else if (rt == Trigger::RunAll) {
|
2021-08-08 21:08:16 -04:00
|
|
|
|
|
|
|
/* trigger is just running normally, and will fill
|
|
|
|
* buffers entirely.
|
|
|
|
*/
|
|
|
|
|
|
|
|
dest_offset = 0;
|
|
|
|
trigger_samples = nframes;
|
2021-08-10 22:35:39 -04:00
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
} else {
|
|
|
|
/* NOTREACHED */
|
2021-08-10 22:35:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (was_waiting_to_start) {
|
2021-09-05 01:19:47 -04:00
|
|
|
determine_next_trigger (currently_playing->index());
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
AudioTrigger* at = dynamic_cast<AudioTrigger*> (currently_playing);
|
2021-08-08 21:08:16 -04:00
|
|
|
|
|
|
|
if (at) {
|
|
|
|
|
|
|
|
boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (r);
|
2021-09-10 15:12:14 -04:00
|
|
|
const uint64_t nchans = ar->n_channels ();
|
2021-08-08 21:08:16 -04:00
|
|
|
|
|
|
|
max_chans = std::max (max_chans, nchans);
|
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
at->run (bufs, trigger_samples, dest_offset, first);
|
|
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
|
|
} else {
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
/* XXX MIDI triggers to be implemented */
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
if (currently_playing->state() == Trigger::Stopped) {
|
|
|
|
|
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 did stop\n", currently_playing->index()));
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
Trigger* nxt = get_next_trigger ();
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
if (nxt) {
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-09-05 01:19:47 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 switching to %2\n", currently_playing->index(), nxt->index()));
|
2021-09-04 12:37:36 -04:00
|
|
|
if (nxt->legato()) {
|
2021-09-05 01:19:47 -04:00
|
|
|
nxt->set_legato_offset (currently_playing->current_pos());
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
2021-09-04 12:37:36 -04:00
|
|
|
/* start it up */
|
|
|
|
nxt->startup();
|
2021-09-05 01:19:47 -04:00
|
|
|
currently_playing = nxt;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
currently_playing = 0;
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
2021-09-05 01:19:47 -04:00
|
|
|
|
|
|
|
} else {
|
|
|
|
/* done */
|
|
|
|
break;
|
2021-08-10 22:35:39 -04:00
|
|
|
}
|
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
ChanCount cc (DataType::AUDIO, max_chans);
|
|
|
|
cc.set_midi (bufs.count().n_midi());
|
|
|
|
bufs.set_count (cc);
|
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
void
|
2021-09-10 15:12:14 -04:00
|
|
|
TriggerBox::prepare_next (uint64_t current)
|
2021-09-04 12:37:36 -04:00
|
|
|
{
|
|
|
|
int nxt = determine_next_trigger (current);
|
|
|
|
|
2021-09-05 12:40:58 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Triggers, string_compose ("nxt for %1 = %2\n", current, nxt));
|
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
if (nxt >= 0) {
|
|
|
|
queue_implicit (all_triggers[nxt]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2021-09-10 15:12:14 -04:00
|
|
|
TriggerBox::determine_next_trigger (uint64_t current)
|
2021-08-10 22:35:39 -04:00
|
|
|
{
|
2021-09-10 15:12:14 -04:00
|
|
|
uint64_t n;
|
|
|
|
uint64_t runnable = 0;
|
2021-08-10 22:35:39 -04:00
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
/* count number of triggers that can actually be run (i.e. they have a region) */
|
|
|
|
|
2021-09-10 15:12:14 -04:00
|
|
|
for (uint64_t n = 0; n < all_triggers.size(); ++n) {
|
2021-08-10 22:35:39 -04:00
|
|
|
if (all_triggers[n]->region()) {
|
|
|
|
runnable++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
/* decide which of the two follow actions we're going to use (based on
|
|
|
|
* random number and the probability setting)
|
|
|
|
*/
|
|
|
|
|
2021-08-12 01:03:53 -04:00
|
|
|
int which_follow_action;
|
2021-08-15 10:04:08 -04:00
|
|
|
int r = _pcg.rand (100); // 0 .. 99
|
2021-08-12 01:03:53 -04:00
|
|
|
|
|
|
|
if (r <= all_triggers[current]->follow_action_probability()) {
|
|
|
|
which_follow_action = 0;
|
|
|
|
} else {
|
|
|
|
which_follow_action = 1;
|
|
|
|
}
|
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
/* first switch: deal with the "special" cases where we either do
|
2021-09-05 01:19:47 -04:00
|
|
|
* nothing or just repeat the current trigger
|
2021-09-04 12:37:36 -04:00
|
|
|
*/
|
|
|
|
|
2021-08-12 01:03:53 -04:00
|
|
|
switch (all_triggers[current]->follow_action (which_follow_action)) {
|
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
case Trigger::Stop:
|
2021-09-04 12:37:36 -04:00
|
|
|
return -1;
|
2021-08-12 01:03:53 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
case Trigger::QueuedTrigger:
|
|
|
|
/* XXX implement me */
|
2021-09-04 12:37:36 -04:00
|
|
|
return -1;
|
2021-08-10 22:35:39 -04:00
|
|
|
default:
|
|
|
|
if (runnable == 1) {
|
2021-09-04 12:37:36 -04:00
|
|
|
/* there's only 1 runnable trigger, so the "next" one
|
|
|
|
is the same as the current one.
|
|
|
|
*/
|
|
|
|
return current;
|
2021-08-10 22:35:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-04 12:37:36 -04:00
|
|
|
/* second switch: handle the "real" follow actions */
|
|
|
|
|
2021-08-12 01:03:53 -04:00
|
|
|
switch (all_triggers[current]->follow_action (which_follow_action)) {
|
2021-08-12 00:54:13 -04:00
|
|
|
|
|
|
|
case Trigger::Again:
|
2021-09-04 12:37:36 -04:00
|
|
|
return current;
|
2021-08-12 00:54:13 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
case Trigger::NextTrigger:
|
|
|
|
n = current;
|
|
|
|
while (true) {
|
|
|
|
++n;
|
|
|
|
|
|
|
|
if (n >= all_triggers.size()) {
|
|
|
|
n = 0;
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
if (n == current) {
|
2021-09-05 12:40:58 -04:00
|
|
|
cerr << "outa here\n";
|
2021-08-10 22:35:39 -04:00
|
|
|
break;
|
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
if (all_triggers[n]->region() && !all_triggers[n]->active()) {
|
2021-09-04 12:37:36 -04:00
|
|
|
return n;
|
2021-08-10 22:35:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Trigger::PrevTrigger:
|
|
|
|
n = current;
|
|
|
|
while (true) {
|
|
|
|
if (n == 0) {
|
|
|
|
n = all_triggers.size() - 1;
|
|
|
|
} else {
|
|
|
|
n -= 1;
|
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
if (n == current) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (all_triggers[n]->region() && !all_triggers[n]->active ()) {
|
2021-09-04 12:37:36 -04:00
|
|
|
return n;
|
2021-08-10 22:35:39 -04:00
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
2021-08-10 22:35:39 -04:00
|
|
|
break;
|
2021-08-08 21:08:16 -04:00
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
case Trigger::FirstTrigger:
|
|
|
|
for (n = 0; n < all_triggers.size(); ++n) {
|
|
|
|
if (all_triggers[n]->region() && !all_triggers[n]->active ()) {
|
2021-09-04 12:37:36 -04:00
|
|
|
return n;
|
2021-08-10 22:35:39 -04:00
|
|
|
}
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
2021-08-10 22:35:39 -04:00
|
|
|
break;
|
|
|
|
case Trigger::LastTrigger:
|
2021-08-11 20:14:37 -04:00
|
|
|
for (int i = all_triggers.size() - 1; i >= 0; --i) {
|
|
|
|
if (all_triggers[i]->region() && !all_triggers[i]->active ()) {
|
2021-09-04 12:37:36 -04:00
|
|
|
return i;
|
2021-08-10 22:35:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Trigger::AnyTrigger:
|
|
|
|
while (true) {
|
2021-08-15 10:04:08 -04:00
|
|
|
n = _pcg.rand (all_triggers.size());
|
2021-08-10 22:35:39 -04:00
|
|
|
if (!all_triggers[n]->region()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (all_triggers[n]->active()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-09-04 12:37:36 -04:00
|
|
|
return n;
|
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
|
|
|
|
case Trigger::OtherTrigger:
|
|
|
|
while (true) {
|
2021-08-15 10:04:08 -04:00
|
|
|
n = _pcg.rand (all_triggers.size());
|
2021-09-10 15:12:14 -04:00
|
|
|
if ((uint64_t) n == current) {
|
2021-08-10 22:35:39 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!all_triggers[n]->region()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (all_triggers[n]->active()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-09-04 12:37:36 -04:00
|
|
|
return n;
|
|
|
|
|
2021-08-10 22:35:39 -04:00
|
|
|
|
|
|
|
/* NOTREACHED */
|
|
|
|
case Trigger::Stop:
|
|
|
|
case Trigger::QueuedTrigger:
|
|
|
|
break;
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
2021-09-04 12:37:36 -04:00
|
|
|
|
|
|
|
return current;
|
2021-08-08 21:08:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode&
|
|
|
|
TriggerBox::get_state (void)
|
|
|
|
{
|
2021-08-31 18:46:19 -04:00
|
|
|
XMLNode& node (Processor::get_state ());
|
|
|
|
|
2021-08-31 21:03:32 -04:00
|
|
|
node.set_property (X_("type"), X_("triggerbox"));
|
2021-08-31 18:46:19 -04:00
|
|
|
node.set_property (X_("data-type"), _data_type.to_string());
|
|
|
|
|
|
|
|
XMLNode* trigger_child (new XMLNode (X_("Triggers")));
|
|
|
|
|
|
|
|
{
|
|
|
|
Glib::Threads::RWLock::ReaderLock lm (trigger_lock);
|
|
|
|
for (Triggers::iterator t = all_triggers.begin(); t != all_triggers.end(); ++t) {
|
|
|
|
trigger_child->add_child_nocopy ((*t)->get_state());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
node.add_child_nocopy (*trigger_child);
|
|
|
|
|
|
|
|
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-08-31 21:03:32 -04:00
|
|
|
node.get_property (X_("data-type"), _data_type);
|
|
|
|
|
|
|
|
XMLNode* tnode (node.child (X_("Triggers")));
|
|
|
|
assert (tnode);
|
|
|
|
|
|
|
|
XMLNodeList const & tchildren (tnode->children());
|
|
|
|
|
|
|
|
drop_triggers ();
|
|
|
|
|
|
|
|
{
|
|
|
|
Glib::Threads::RWLock::WriterLock lm (trigger_lock);
|
|
|
|
|
|
|
|
for (XMLNodeList::const_iterator t = tchildren.begin(); t != tchildren.end(); ++t) {
|
|
|
|
Trigger* trig;
|
|
|
|
|
|
|
|
if (_data_type == DataType::AUDIO) {
|
|
|
|
trig = new AudioTrigger (all_triggers.size(), *this);
|
|
|
|
all_triggers.push_back (trig);
|
|
|
|
trig->set_state (**t, version);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-08 21:08:16 -04:00
|
|
|
return 0;
|
|
|
|
}
|