triggerbox: more substantial changes to the new (justifiable) design; audio triggers seem ok, MIDI triggers untested
This commit is contained in:
parent
59b012ddb0
commit
2b6b7226b0
|
@ -63,13 +63,12 @@ class SideChain;
|
|||
class LIBARDOUR_API Trigger : public PBD::Stateful {
|
||||
public:
|
||||
enum State {
|
||||
None = 0, /* mostly for _requested_state */
|
||||
Stopped = 1,
|
||||
WaitingToStart = 2,
|
||||
Running = 3,
|
||||
WaitingForRetrigger = 4,
|
||||
WaitingToStop = 5,
|
||||
Stopping = 6
|
||||
Stopped,
|
||||
WaitingToStart,
|
||||
Running,
|
||||
WaitingForRetrigger,
|
||||
WaitingToStop,
|
||||
Stopping
|
||||
};
|
||||
|
||||
Trigger (uint64_t index, TriggerBox&);
|
||||
|
@ -86,6 +85,9 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
|||
/* explicitly call for the trigger to stop */
|
||||
void request_stop ();
|
||||
|
||||
virtual pframes_t run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample,
|
||||
Temporal::Beats const & start, Temporal::Beats const & end,
|
||||
pframes_t nframes, pframes_t offset, bool first, double bpm) = 0;
|
||||
virtual void set_start (timepos_t const &) = 0;
|
||||
virtual void set_end (timepos_t const &) = 0;
|
||||
virtual void set_length (timecnt_t const &) = 0;
|
||||
|
@ -116,6 +118,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
|||
void set_launch_style (LaunchStyle);
|
||||
|
||||
enum FollowAction {
|
||||
None,
|
||||
Stop,
|
||||
Again,
|
||||
QueuedTrigger, /* DP-style */
|
||||
|
@ -150,22 +153,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
|
|||
XMLNode& get_state (void);
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
enum RunResult {
|
||||
Relax = 0,
|
||||
RemoveTrigger = 0x1,
|
||||
ReadMore = 0x2,
|
||||
FillSilence = 0x4,
|
||||
ChangeTriggers = 0x8
|
||||
};
|
||||
|
||||
enum RunType {
|
||||
RunEnd,
|
||||
RunStart,
|
||||
RunAll,
|
||||
RunNone,
|
||||
};
|
||||
|
||||
RunType maybe_compute_next_transition (Temporal::Beats const & start, Temporal::Beats const & end);
|
||||
pframes_t maybe_compute_next_transition (samplepos_t start_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t dest_offset, bool passthru);
|
||||
|
||||
void set_next_trigger (int n);
|
||||
int next_trigger() const { return _next_trigger; }
|
||||
|
@ -242,7 +230,7 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
|
|||
AudioTrigger (uint64_t index, TriggerBox&);
|
||||
~AudioTrigger ();
|
||||
|
||||
pframes_t run (BufferSet&, pframes_t nframes, pframes_t offset, bool first, double bpm);
|
||||
pframes_t run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t nframes, pframes_t offset, bool first, double bpm);
|
||||
|
||||
void set_start (timepos_t const &);
|
||||
void set_end (timepos_t const &);
|
||||
|
@ -288,7 +276,6 @@ class LIBARDOUR_API AudioTrigger : public Trigger {
|
|||
|
||||
void drop_data ();
|
||||
int load_data (boost::shared_ptr<AudioRegion>);
|
||||
RunResult at_end ();
|
||||
void determine_tempo ();
|
||||
void setup_stretcher ();
|
||||
};
|
||||
|
@ -299,7 +286,7 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
|
|||
MIDITrigger (uint64_t index, TriggerBox&);
|
||||
~MIDITrigger ();
|
||||
|
||||
pframes_t run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample, pframes_t nframes, pframes_t offset, bool first, double bpm);
|
||||
pframes_t run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample, Temporal::Beats const & start_beats, Temporal::Beats const & end_beats, pframes_t nframes, pframes_t offset, bool passthru, double bpm);
|
||||
|
||||
void set_start (timepos_t const &);
|
||||
void set_end (timepos_t const &);
|
||||
|
@ -342,7 +329,6 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
|
|||
boost::shared_ptr<MidiModel> model;
|
||||
|
||||
int load_data (boost::shared_ptr<MidiRegion>);
|
||||
RunResult at_end ();
|
||||
void compute_and_set_length ();
|
||||
};
|
||||
|
||||
|
|
|
@ -845,7 +845,6 @@ setup_enum_writer ()
|
|||
REGISTER_ENUM (RollIfAppropriate);
|
||||
REGISTER (_LocateTransportDisposition);
|
||||
|
||||
REGISTER_CLASS_ENUM (Trigger, None);
|
||||
REGISTER_CLASS_ENUM (Trigger, Stopped);
|
||||
REGISTER_CLASS_ENUM (Trigger, WaitingToStart);
|
||||
REGISTER_CLASS_ENUM (Trigger, Running);
|
||||
|
|
|
@ -336,10 +336,6 @@ Trigger::process_state_requests ()
|
|||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 handling bang with state = %2\n", index(), enum_2_string (_state)));
|
||||
|
||||
switch (_state) {
|
||||
case None:
|
||||
abort ();
|
||||
break;
|
||||
|
||||
case Running:
|
||||
switch (launch_style()) {
|
||||
case OneShot:
|
||||
|
@ -388,22 +384,20 @@ Trigger::process_state_requests ()
|
|||
}
|
||||
}
|
||||
|
||||
Trigger::RunType
|
||||
Trigger::maybe_compute_next_transition (Temporal::Beats const & start, Temporal::Beats const & end)
|
||||
pframes_t
|
||||
Trigger::maybe_compute_next_transition (samplepos_t start_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t dest_offset, bool passthru)
|
||||
{
|
||||
using namespace Temporal;
|
||||
|
||||
/* This should never be called by a stopped trigger */
|
||||
|
||||
assert (_state != Stopped);
|
||||
|
||||
/* 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;
|
||||
if (_state == Running || _state == Stopping) {
|
||||
/* will cover everything */
|
||||
return dest_offset;
|
||||
}
|
||||
|
||||
timepos_t transition_time (BeatTime);
|
||||
|
@ -412,6 +406,10 @@ Trigger::maybe_compute_next_transition (Temporal::Beats const & start, Temporal:
|
|||
|
||||
/* XXX need to use global grid here is quantization == zero */
|
||||
|
||||
/* Given the value of @param start, determine, based on the
|
||||
* quantization, the next time for a transition.
|
||||
*/
|
||||
|
||||
if (_quantization.bars == 0) {
|
||||
Temporal::Beats transition_beats = start.round_up_to_multiple (Temporal::Beats (_quantization.beats, _quantization.ticks));
|
||||
transition_bbt = tmap->bbt_at (transition_beats);
|
||||
|
@ -425,42 +423,84 @@ Trigger::maybe_compute_next_transition (Temporal::Beats const & start, Temporal:
|
|||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 quantized with %5 transition at %2, sb %3 eb %4\n", index(), transition_time.beats(), start, end, _quantization));
|
||||
|
||||
/* See if this time falls within the range of time given to us */
|
||||
|
||||
if (transition_time.beats() >= start && transition_time < end) {
|
||||
|
||||
/* transition time has arrived! let's figure out what're doing:
|
||||
* stopping, starting, retriggering
|
||||
*/
|
||||
|
||||
transition_samples = transition_time.samples();
|
||||
transition_beats = transition_time.beats ();
|
||||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 in range, should start/stop at %2 aka %3\n", index(), transition_samples, transition_beats));
|
||||
|
||||
if (_state == WaitingToStop) {
|
||||
switch (_state) {
|
||||
|
||||
case WaitingToStop:
|
||||
_state = Stopping;
|
||||
PropertyChanged (ARDOUR::Properties::running);
|
||||
return RunEnd;
|
||||
} else if (_state == WaitingToStart) {
|
||||
retrigger ();
|
||||
_state = Running;
|
||||
expected_end_sample = tmap->sample_at (tmap->bbt_walk(transition_bbt, BBT_Offset (round (_barcnt), 0, 0)));
|
||||
cerr << "starting at " << transition_bbt << " bars " << round(_barcnt) << " end at " << tmap->bbt_walk (transition_bbt, BBT_Offset (round (_barcnt), 0, 0)) << " sample = " << expected_end_sample << endl;
|
||||
PropertyChanged (ARDOUR::Properties::running);
|
||||
return RunStart;
|
||||
} else if (_state == WaitingForRetrigger) {
|
||||
retrigger ();
|
||||
_state = Running;
|
||||
expected_end_sample = tmap->sample_at (tmap->bbt_walk(transition_bbt, BBT_Offset (round (_barcnt), 0, 0)));
|
||||
cerr << "starting at " << transition_bbt << " bars " << round(_barcnt) << " end at " << tmap->bbt_walk (transition_bbt, BBT_Offset (round (_barcnt), 0, 0)) << " sample = " << expected_end_sample << endl;
|
||||
PropertyChanged (ARDOUR::Properties::running);
|
||||
return RunAll;
|
||||
}
|
||||
} else {
|
||||
if (_state == WaitingForRetrigger || _state == WaitingToStop) {
|
||||
/* retrigger time has not been reached, just continue
|
||||
to play normally until then.
|
||||
|
||||
/* trigger will reach it's end somewhere within this
|
||||
* process cycle, so compute the number of samples it
|
||||
* should generate.
|
||||
*/
|
||||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 will stop somewhere in the middle of run()\n", name()));
|
||||
|
||||
/* offset within the buffer(s) for output remains
|
||||
unchanged, since we will write from the first
|
||||
location corresponding to start
|
||||
*/
|
||||
return RunAll;
|
||||
break;
|
||||
|
||||
case WaitingToStart:
|
||||
retrigger ();
|
||||
_state = Running;
|
||||
expected_end_sample = tmap->sample_at (tmap->bbt_walk(transition_bbt, BBT_Offset (round (_barcnt), 0, 0)));
|
||||
cerr << "starting at " << transition_bbt << " bars " << round(_barcnt) << " end at " << tmap->bbt_walk (transition_bbt, BBT_Offset (round (_barcnt), 0, 0)) << " sample = " << expected_end_sample << endl;
|
||||
PropertyChanged (ARDOUR::Properties::running);
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
dest_offset += std::max (samplepos_t (0), transition_samples - start_sample);
|
||||
|
||||
if (!passthru) {
|
||||
/* XXX need to silence start of buffers up to dest_offset */
|
||||
}
|
||||
break;
|
||||
|
||||
case WaitingForRetrigger:
|
||||
retrigger ();
|
||||
_state = Running;
|
||||
expected_end_sample = tmap->sample_at (tmap->bbt_walk(transition_bbt, BBT_Offset (round (_barcnt), 0, 0)));
|
||||
cerr << "starting at " << transition_bbt << " bars " << round(_barcnt) << " end at " << tmap->bbt_walk (transition_bbt, BBT_Offset (round (_barcnt), 0, 0)) << " sample = " << expected_end_sample << endl;
|
||||
PropertyChanged (ARDOUR::Properties::running);
|
||||
|
||||
/* trigger is just running normally, and will fill
|
||||
* buffers entirely.
|
||||
*/
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
fatal << string_compose (_("programming error: %1"), "impossible trigger state in ::maybe_compute_next_transition()") << endmsg;
|
||||
abort();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/* retrigger time has not been reached, just continue
|
||||
to play normally until then.
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
return RunNone;
|
||||
return dest_offset;
|
||||
}
|
||||
|
||||
/*--------------------*/
|
||||
|
@ -778,7 +818,7 @@ AudioTrigger::retrigger ()
|
|||
}
|
||||
|
||||
pframes_t
|
||||
AudioTrigger::run (BufferSet& bufs, pframes_t nframes, pframes_t dest_offset, bool passthru, double bpm)
|
||||
AudioTrigger::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, bool passthru, double bpm)
|
||||
{
|
||||
boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(_region);
|
||||
const uint32_t nchans = ar->n_channels();
|
||||
|
@ -788,8 +828,23 @@ AudioTrigger::run (BufferSet& bufs, pframes_t nframes, pframes_t dest_offset, bo
|
|||
Sample* bufp[nchans];
|
||||
const bool stretching = (_apparent_tempo != 0.);
|
||||
|
||||
assert (ar);
|
||||
assert (active());
|
||||
/* see if we're going to start or stop or retrigger in this run() call */
|
||||
dest_offset = maybe_compute_next_transition (start_sample, start, end, dest_offset, passthru);
|
||||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 after checking for transition, state = %2\n", name(), enum_2_string (_state)));
|
||||
|
||||
switch (_state) {
|
||||
case Stopped:
|
||||
case WaitingForRetrigger:
|
||||
case WaitingToStart:
|
||||
/* did everything we could do */
|
||||
return nframes;
|
||||
case Running:
|
||||
case WaitingToStop:
|
||||
case Stopping:
|
||||
/* stuff to do */
|
||||
break;
|
||||
}
|
||||
|
||||
/* We use session scratch buffers for both padding the start of the
|
||||
* input to RubberBand, and to hold the output. Because of this dual
|
||||
|
@ -1209,7 +1264,9 @@ MIDITrigger::reload (BufferSet&, void*)
|
|||
}
|
||||
|
||||
pframes_t
|
||||
MIDITrigger::run (BufferSet& bufs, samplepos_t start, samplepos_t end, pframes_t nframes, pframes_t dest_offset, bool passthru, double bpm)
|
||||
MIDITrigger::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, bool passthru, double bpm)
|
||||
{
|
||||
MidiBuffer& mb (bufs.get_midi (0));
|
||||
typedef Evoral::Event<MidiModel::TimeType> MidiEvent;
|
||||
|
@ -1218,11 +1275,25 @@ MIDITrigger::run (BufferSet& bufs, samplepos_t start, samplepos_t end, pframes_t
|
|||
Temporal::TempoMap::SharedPtr tmap (Temporal::TempoMap::use());
|
||||
samplepos_t last_event_samples = 0;
|
||||
|
||||
/* see if we're going to start or stop or retrigger in this run() call */
|
||||
dest_offset = maybe_compute_next_transition (start_sample, start_beats, end_beats, dest_offset, passthru);
|
||||
|
||||
switch (_state) {
|
||||
case Stopped:
|
||||
case WaitingForRetrigger:
|
||||
case WaitingToStart:
|
||||
return nframes;
|
||||
case Running:
|
||||
case WaitingToStop:
|
||||
case Stopping:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!passthru) {
|
||||
mb.clear ();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
while (iter != model->end()) {
|
||||
|
||||
MidiEvent const & next_event (*iter);
|
||||
|
||||
|
@ -1238,15 +1309,27 @@ MIDITrigger::run (BufferSet& bufs, samplepos_t start, samplepos_t end, pframes_t
|
|||
|
||||
const samplepos_t timeline_samples = tmap->sample_at (effective_time);
|
||||
|
||||
if (timeline_samples > end) {
|
||||
if (timeline_samples >= end_sample) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Now we have to convert to a position within the buffer we
|
||||
* are writing to.
|
||||
*
|
||||
* There's a slight complication here, because both
|
||||
* start_sample and dest_offset reflect an offset from the
|
||||
* start of the buffer that our parent (TriggerBox) processor
|
||||
* is handling in its own run() method. start_sample may have
|
||||
* been adjusted to reflect a previous Trigger's processing
|
||||
* during this run cycle, and so has dest_offset.
|
||||
*
|
||||
* Therefore, when computing the buffer sample for this MIDI
|
||||
* event, we only consider one of the two values, not both. If
|
||||
* we use both, we will double the "offset" that occurs if
|
||||
* another Trigger ran during this process cycle.
|
||||
*/
|
||||
|
||||
samplepos_t buffer_samples = timeline_samples - start + dest_offset;
|
||||
samplepos_t buffer_samples = timeline_samples - start_sample + dest_offset;
|
||||
last_event_samples = buffer_samples;
|
||||
|
||||
const Evoral::Event<MidiBuffer::TimeType> ev (Evoral::MIDI_EVENT, buffer_samples, next_event.size(), const_cast<uint8_t*>(next_event.buffer()), false);
|
||||
|
@ -1256,49 +1339,54 @@ MIDITrigger::run (BufferSet& bufs, samplepos_t start, samplepos_t end, pframes_t
|
|||
last_event_beats = next_event.time();
|
||||
|
||||
++iter;
|
||||
|
||||
if (iter == model->end()) {
|
||||
|
||||
/* We reached the end */
|
||||
|
||||
_loop_cnt++;
|
||||
_state = Stopped;
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached end, now stopped\n", index()));
|
||||
}
|
||||
}
|
||||
|
||||
if (last_event_samples) {
|
||||
nframes -= (last_event_samples - start);
|
||||
}
|
||||
|
||||
if (_state == Stopping) {
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 was stopping, now stopped\n", index()));
|
||||
tracker.resolve_notes (mb, nframes);
|
||||
shutdown ();
|
||||
}
|
||||
|
||||
if (_state == Stopped) {
|
||||
if (_loop_cnt == _follow_count) {
|
||||
/* have played the specified number of times, we're done */
|
||||
if (iter == model->end()) {
|
||||
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 loop cnt %2 satisfied, now stopped\n", index(), _follow_count));
|
||||
shutdown ();
|
||||
/* We reached the end */
|
||||
|
||||
} else {
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 reached end\n", index()));
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
if (!(_state == Stopping)) {
|
||||
_loop_cnt++;
|
||||
|
||||
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.
|
||||
*/
|
||||
transition_beats = transition_beats + data_length;
|
||||
retrigger ();
|
||||
_state = WaitingToStart;
|
||||
if (_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));
|
||||
shutdown ();
|
||||
|
||||
} 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.
|
||||
*/
|
||||
transition_beats = transition_beats + data_length;
|
||||
retrigger ();
|
||||
_state = WaitingToStart;
|
||||
}
|
||||
}
|
||||
|
||||
/* the time we processed spans from start to the last event */
|
||||
|
||||
nframes -= (last_event_samples - start_sample);
|
||||
|
||||
} else {
|
||||
/* we didn't reach the end of the MIDI data, ergo we covered
|
||||
the entire timespan passed into us.
|
||||
*/
|
||||
}
|
||||
|
||||
return nframes;
|
||||
|
@ -1724,7 +1812,7 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp
|
|||
|
||||
if (!check_active()) {
|
||||
return;
|
||||
}
|
||||
|