13
0

modify MIDITrigger to use an RTMidiBuffer<Beats,Beats> for playback, not a MidiModel

Creating an iterator on a MidiModel (Sequence) creates a read-lock on the same,
which exists until the iterator is destroyed.

This new designs renders the model/source to an RTMidiBuffer, then atomically
swaps in a new RTMidiBuffer in an RT context.

Not yet implemented in this temporary branch is handling required
state-changing messages when the buffer is swapped.
This commit is contained in:
Paul Davis 2024-06-25 22:05:41 -06:00
parent a4a277c141
commit e3d790207f
2 changed files with 82 additions and 24 deletions

View File

@ -48,6 +48,7 @@
#include "ardour/midi_model.h"
#include "ardour/midi_state_tracker.h"
#include "ardour/processor.h"
#include "ardour/rt_midibuffer.h"
#include "ardour/segment_descriptor.h"
#include "ardour/types.h"
#include "ardour/types_convert.h"
@ -415,6 +416,8 @@ class LIBARDOUR_API Trigger : public PBD::Stateful {
void get_ui_state (UIState &state) const;
void set_ui_state (UIState &state);
virtual void check_edit_swap (timepos_t const & time, bool playing, BufferSet& bufs) {}
static PBD::Signal2<void,PBD::PropertyChange,Trigger*> TriggerPropertyChange;
protected:
@ -624,6 +627,8 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
int channel_map (int channel);
std::vector<int> const & channel_map() const { return _channel_map; }
void check_edit_swap (timepos_t const &, bool playing, BufferSet&);
protected:
void retrigger ();
@ -640,9 +645,14 @@ class LIBARDOUR_API MIDITrigger : public Trigger {
Temporal::BBT_Offset _start_offset;
Temporal::BBT_Offset _legato_offset;
std::shared_ptr<MidiModel> model;
MidiModel::const_iterator iter;
bool map_change;
typedef RTMidiBufferBase<Temporal::Beats,Temporal::Beats> RTMidiBufferBeats;
std::atomic<RTMidiBufferBeats*> rt_midibuffer;
std::atomic<RTMidiBufferBeats*> pending_rt_midibuffer;
std::atomic<RTMidiBufferBeats*> old_rt_midibuffer;
uint32_t iter;
bool map_change;
int load_data (std::shared_ptr<MidiRegion>);
void compute_and_set_length ();

View File

@ -44,7 +44,9 @@
#include "ardour/debug.h"
#include "ardour/import_status.h"
#include "ardour/midi_buffer.h"
#include "ardour/midi_cursor.h"
#include "ardour/midi_model.h"
#include "ardour/midi_state_tracker.h"
#include "ardour/midi_region.h"
#include "ardour/minibpm.h"
#include "ardour/port.h"
@ -1270,6 +1272,31 @@ AudioTrigger::~AudioTrigger ()
delete _stretcher;
}
void
MIDITrigger::check_edit_swap (timepos_t const & time, bool playing, BufferSet& bufs)
{
RTMidiBufferBeats* pending = pending_rt_midibuffer.load();
if (!pending) {
return;
}
// if (playing) {
MidiBuffer& mbuf (bufs.get_midi (0));
MidiStateTracker post_edit_state;
const timepos_t region_start_time = _region->start();
const Temporal::Beats region_start = region_start_time.beats();
pending->track_state (transition_beats + (time.beats() - region_start), post_edit_state);
// _box.tracker->resolve_diff (post_edit_state, mbuf, time.samples());
// }
old_rt_midibuffer = rt_midibuffer.exchange (pending);
std::cerr << "Swapped in new pending RT MB\n";
pending_rt_midibuffer = nullptr;
}
void
AudioTrigger::set_stretch_mode (Trigger::StretchMode sm)
{
@ -2174,6 +2201,9 @@ MIDITrigger::MIDITrigger (uint32_t n, TriggerBox& b)
, last_event_samples (0)
, _start_offset (0, 0, 0)
, _legato_offset (0, 0, 0)
, rt_midibuffer (nullptr)
, pending_rt_midibuffer (nullptr)
, old_rt_midibuffer (nullptr)
, map_change (false)
{
_channel_map.assign (16, -1);
@ -2664,7 +2694,13 @@ MIDITrigger::set_region_in_worker_thread (std::shared_ptr<Region> r)
data_length = mr->length().beats();
_follow_length = Temporal::BBT_Offset (0, data_length.get_beats(), 0);
set_length (mr->length());
model = mr->model ();
pending_rt_midibuffer = new RTMidiBufferBeats;
{
Source::ReaderLock lm (mr->midi_source()->mutex());
mr->midi_source()->render (lm, *pending_rt_midibuffer);
}
estimate_midi_patches ();
@ -2687,11 +2723,11 @@ MIDITrigger::retrigger ()
/* XXX need to deal with bar offsets */
// const Temporal::BBT_Offset o = _start_offset + _legato_offset;
iter = model->begin();
iter = 0;
_legato_offset = Temporal::BBT_Offset ();
last_event_beats = Temporal::Beats();
last_event_samples = 0;
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 retriggered to %2, ts = %3\n", _index, iter->time(), transition_beats));
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 retriggered to start, ts = %2\n", _index, transition_beats));
}
void
@ -2706,12 +2742,13 @@ MIDITrigger::tempo_map_changed ()
* on an active trigger.
*/
iter = model->begin();
iter = 0;
Temporal::TempoMap::SharedPtr tmap (Temporal::TempoMap::use());
const timepos_t region_start_time = _region->start();
const Temporal::Beats region_start = region_start_time.beats();
RTMidiBufferBeats* rtmb (rt_midibuffer.load());
while (iter != model->end()) {
while (iter < rtmb->size()) {
/* Find the first event whose sample time is equal-to or
* greater than the last played event sample. That is the
@ -2722,7 +2759,9 @@ MIDITrigger::tempo_map_changed ()
* one.
*/
const Temporal::Beats iter_timeline_beats = transition_beats + ((*iter).time() - region_start);
RTMidiBufferBeats::Item const & item ((*rtmb)[iter]);
// const Temporal::Beats iter_timeline_beats =
Temporal::Beats iter_timeline_beats = transition_beats + (item.timestamp - region_start);
samplepos_t iter_timeline_samples = tmap->sample_at (iter_timeline_beats);
if (iter_timeline_samples >= last_event_samples) {
@ -2732,7 +2771,7 @@ MIDITrigger::tempo_map_changed ()
++iter;
}
if (iter != model->end()) {
if (iter < rtmb->size()) {
Temporal::Beats elen_ignored;
(void) compute_end (tmap, _transition_bbt, transition_samples, elen_ignored);
}
@ -2746,6 +2785,8 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
Temporal::Beats const & start_beats, Temporal::Beats const & end_beats,
pframes_t nframes, pframes_t dest_offset, double bpm, pframes_t& quantize_offset)
{
assert (rt_midibuffer);
MidiBuffer* mb (in_process_context? &bufs.get_midi (0) : 0);
typedef Evoral::Event<MidiModel::TimeType> MidiEvent;
const timepos_t region_start_time = _region->start();
@ -2758,8 +2799,9 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
quantize_offset = 0;
maybe_compute_next_transition (start_sample, start_beats, end_beats, nframes, quantize_offset);
const pframes_t orig_nframes = nframes;
RTMidiBufferBeats* rtmb (rt_midibuffer.load());
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 after checking for transition, state = %2\n", name(), enum_2_string (_state)));
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 after checking for transition, state = %2 iter @ %3 total %4\n", name(), enum_2_string (_state), iter, rtmb->size()));
switch (_state) {
case Stopped:
@ -2775,18 +2817,17 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
Temporal::Beats last_event_timeline_beats = final_beat; /* will indicate "done" if there is nothing to do */
while (iter != model->end() && !_playout) {
while (iter < rtmb->size() && !_playout) {
MidiEvent const & event (*iter);
RTMidiBufferBeats::Item const & item ((*rtmb)[iter]);
/* Event times are in beats, relative to start of source
* file. We need to convert to region-relative time, and then
* file. We need to conv+ert 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)
*/
Temporal::Beats maybe_last_event_timeline_beats = transition_beats + (event.time() - region_start);
Temporal::Beats maybe_last_event_timeline_beats = transition_beats + (item.timestamp - region_start);
/* check that the event is within the bounds for this run() call */
@ -2795,7 +2836,7 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
}
if (maybe_last_event_timeline_beats > final_beat) {
iter = model->end();
iter = rtmb->size();
break;
}
@ -2807,6 +2848,9 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
const samplepos_t timeline_samples = tmap->sample_at (maybe_last_event_timeline_beats);
uint32_t evsize;
uint8_t const * buf = rtmb->bytes (item, evsize);
if (in_process_context) { /* compile-time const expr */
/* Now we have to convert to a position within the buffer we
@ -2864,7 +2908,7 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
assert (buffer_samples >= 0);
Evoral::Event<MidiBuffer::TimeType> ev (Evoral::MIDI_EVENT, buffer_samples, event.size(), const_cast<uint8_t*>(event.buffer()), false);
Evoral::Event<MidiBuffer::TimeType> ev (Evoral::MIDI_EVENT, buffer_samples, evsize, const_cast<uint8_t*>(buf), false);
if (_gain != 1.0f && ev.is_note()) {
ev.scale_velocity (_gain);
@ -2900,9 +2944,9 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
mb->insert_event (ev);
}
_box.tracker->track (event.buffer());
_box.tracker->track (buf);
last_event_beats = event.time();
last_event_beats = item.timestamp;
last_event_timeline_beats = maybe_last_event_timeline_beats;
++iter;
@ -2914,7 +2958,7 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
_box.tracker->resolve_notes (*mb, nframes-1);
}
if (iter == model->end()) {
if (iter >= rtmb->size()) {
/* We reached the end */
@ -2922,8 +2966,8 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
/* "final_beat" is an inclusive end of the trigger, not
* exclusive, so we must use <= here. That is, any last event
* (remember, iter == model->end() here, so we have already read
* through the entire MIDI model) that is up to AND INCLUDING
* (remember, iter == rt_midibuffer->size() here, so we have already read
* through the entire RTMidiBuffer) that is up to AND INCLUDING
* final_beat counts as "haven't reached the end".
*/
@ -2981,7 +3025,7 @@ MIDITrigger::midi_run (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
/* we didn't reach the end of the MIDI data, ergo we covered
the entire timespan passed into us.
*/
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 did not reach end, nframes left at %2, next event is %3\n", index(), nframes, *iter));
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1 did not reach end, nframes left at %2, next event is %3\n", index(), nframes, iter));
nframes = 0;
}
@ -4217,6 +4261,10 @@ TriggerBox::run_cycle (BufferSet& bufs, samplepos_t start_sample, samplepos_t en
}
}
for (uint64_t n = 0; n < all_triggers.size(); ++n) {
all_triggers[n]->check_edit_swap (timepos_t (start_sample), _currently_playing == all_triggers[n], bufs);
}
/* STEP FOUR: handle any incoming requests from the GUI or other
* non-MIDI UIs
*/