13
0

change all MIDI read-from-source to map all events into the loop-range for seamless looping (if using)

This commit is contained in:
Paul Davis 2016-09-13 14:10:04 -05:00
parent 182e35235c
commit f41bc70ee9
12 changed files with 245 additions and 169 deletions

View File

@ -70,6 +70,7 @@ public:
* @param buf Destination for events. * @param buf Destination for events.
* @param start First frame of read range. * @param start First frame of read range.
* @param cnt Number of frames in read range. * @param cnt Number of frames in read range.
* @param loop_range If non-null, all event times will be mapped into this loop range.
* @param chan_n Must be 0 (this is the audio-style "channel", where each * @param chan_n Must be 0 (this is the audio-style "channel", where each
* channel is backed by a separate region, not MIDI channels, which all * channel is backed by a separate region, not MIDI channels, which all
* exist in the same region and are not handled here). * exist in the same region and are not handled here).
@ -78,6 +79,7 @@ public:
framecnt_t read (Evoral::EventSink<framepos_t>& buf, framecnt_t read (Evoral::EventSink<framepos_t>& buf,
framepos_t start, framepos_t start,
framecnt_t cnt, framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
uint32_t chan_n = 0, uint32_t chan_n = 0,
MidiChannelFilter* filter = NULL); MidiChannelFilter* filter = NULL);

View File

@ -65,6 +65,7 @@ protected:
framepos_t position, framepos_t position,
framepos_t start, framepos_t start,
framecnt_t cnt, framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
MidiStateTracker* tracker, MidiStateTracker* tracker,
MidiChannelFilter* filter) const; MidiChannelFilter* filter) const;

View File

@ -24,6 +24,7 @@
#include <vector> #include <vector>
#include "evoral/Beats.hpp" #include "evoral/Beats.hpp"
#include "evoral/Range.hpp"
#include "ardour/ardour.h" #include "ardour/ardour.h"
#include "ardour/region.h" #include "ardour/region.h"
@ -75,6 +76,7 @@ class LIBARDOUR_API MidiRegion : public Region
framecnt_t read_at (Evoral::EventSink<framepos_t>& dst, framecnt_t read_at (Evoral::EventSink<framepos_t>& dst,
framepos_t position, framepos_t position,
framecnt_t dur, framecnt_t dur,
Evoral::Range<framepos_t>* loop_range,
uint32_t chan_n = 0, uint32_t chan_n = 0,
NoteMode mode = Sustained, NoteMode mode = Sustained,
MidiStateTracker* tracker = 0, MidiStateTracker* tracker = 0,
@ -83,6 +85,7 @@ class LIBARDOUR_API MidiRegion : public Region
framecnt_t master_read_at (MidiRingBuffer<framepos_t>& dst, framecnt_t master_read_at (MidiRingBuffer<framepos_t>& dst,
framepos_t position, framepos_t position,
framecnt_t dur, framecnt_t dur,
Evoral::Range<framepos_t>* loop_range,
uint32_t chan_n = 0, uint32_t chan_n = 0,
NoteMode mode = Sustained) const; NoteMode mode = Sustained) const;
@ -130,6 +133,7 @@ class LIBARDOUR_API MidiRegion : public Region
framecnt_t _read_at (const SourceList&, Evoral::EventSink<framepos_t>& dst, framecnt_t _read_at (const SourceList&, Evoral::EventSink<framepos_t>& dst,
framepos_t position, framepos_t position,
framecnt_t dur, framecnt_t dur,
Evoral::Range<framepos_t>* loop_range,
uint32_t chan_n = 0, uint32_t chan_n = 0,
NoteMode mode = Sustained, NoteMode mode = Sustained,
MidiStateTracker* tracker = 0, MidiStateTracker* tracker = 0,

View File

@ -27,6 +27,7 @@
#include "pbd/stateful.h" #include "pbd/stateful.h"
#include "pbd/xml++.h" #include "pbd/xml++.h"
#include "evoral/Sequence.hpp" #include "evoral/Sequence.hpp"
#include "evoral/Range.hpp"
#include "ardour/ardour.h" #include "ardour/ardour.h"
#include "ardour/buffer.h" #include "ardour/buffer.h"
#include "ardour/source.h" #include "ardour/source.h"
@ -82,14 +83,17 @@ class LIBARDOUR_API MidiSource : virtual public Source, public boost::enable_sha
* \param source_start Start position of the SOURCE in this read context. * \param source_start Start position of the SOURCE in this read context.
* \param start Start of range to be read. * \param start Start of range to be read.
* \param cnt Length of range to be read (in audio frames). * \param cnt Length of range to be read (in audio frames).
* \param loop_range If non-null, all event times will be mapped into this loop range.
* \param tracker an optional pointer to MidiStateTracker object, for note on/off tracking. * \param tracker an optional pointer to MidiStateTracker object, for note on/off tracking.
* \param filtered Parameters whose MIDI messages will not be returned. * \param filtered Parameters whose MIDI messages will not be returned.
*/ */
virtual framecnt_t midi_read (const Lock& lock, virtual framecnt_t midi_read (const Lock& lock,
Evoral::EventSink<framepos_t>& dst, Evoral::EventSink<framepos_t>& dst,
framepos_t source_start, framepos_t source_start,
framepos_t start, framepos_t start,
framecnt_t cnt, framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
MidiStateTracker* tracker, MidiStateTracker* tracker,
MidiChannelFilter* filter, MidiChannelFilter* filter,
const std::set<Evoral::Parameter>& filtered, const std::set<Evoral::Parameter>& filtered,
@ -210,6 +214,7 @@ class LIBARDOUR_API MidiSource : virtual public Source, public boost::enable_sha
framepos_t position, framepos_t position,
framepos_t start, framepos_t start,
framecnt_t cnt, framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
MidiStateTracker* tracker, MidiStateTracker* tracker,
MidiChannelFilter* filter) const = 0; MidiChannelFilter* filter) const = 0;

View File

@ -94,6 +94,7 @@ public:
framepos_t position, framepos_t position,
framepos_t start, framepos_t start,
framecnt_t cnt, framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
MidiStateTracker* tracker, MidiStateTracker* tracker,
MidiChannelFilter* filter) const; MidiChannelFilter* filter) const;

View File

@ -360,7 +360,8 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
framepos_t loop_start = 0; framepos_t loop_start = 0;
framepos_t loop_end = 0; framepos_t loop_end = 0;
framepos_t loop_length = 0; framepos_t loop_length = 0;
get_location_times(loop_loc, &loop_start, &loop_end, &loop_length);
get_location_times (loop_loc, &loop_start, &loop_end, &loop_length);
adjust_capture_position = 0; adjust_capture_position = 0;
@ -506,7 +507,7 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
playback_distance = nframes; playback_distance = nframes;
} }
if (need_disk_signal) { if (need_disk_signal && !_session.declick_out_pending()) {
/* copy the diskstream data to all output buffers */ /* copy the diskstream data to all output buffers */
MidiBuffer& mbuf (bufs.get_midi (0)); MidiBuffer& mbuf (bufs.get_midi (0));
@ -703,42 +704,48 @@ int
MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed) MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
{ {
framecnt_t this_read = 0; framecnt_t this_read = 0;
bool reloop = false;
framepos_t loop_end = 0; framepos_t loop_end = 0;
framepos_t loop_start = 0; framepos_t loop_start = 0;
framecnt_t loop_length = 0; framecnt_t loop_length = 0;
Location* loc = 0; Location* loc = loop_location;
framepos_t effective_start = start;
Evoral::Range<framepos_t>* loop_range (0);
MidiTrack* mt = dynamic_cast<MidiTrack*>(_track); MidiTrack* mt = dynamic_cast<MidiTrack*>(_track);
MidiChannelFilter* filter = mt ? &mt->playback_filter() : NULL; MidiChannelFilter* filter = mt ? &mt->playback_filter() : NULL;
if (!reversed) { frameoffset_t loop_offset = 0;
loc = loop_location; if (!reversed && loc) {
get_location_times(loc, &loop_start, &loop_end, &loop_length); get_location_times (loc, &loop_start, &loop_end, &loop_length);
/* if we are looping, ensure that the first frame we read is at the correct
position within the loop.
*/
if (loc && (start >= loop_end)) {
//cerr << "start adjusted from " << start;
start = loop_start + ((start - loop_start) % loop_length);
//cerr << "to " << start << endl;
}
// cerr << "start is " << start << " end " << start+dur << " loopstart: " << loop_start << " loopend: " << loop_end << endl;
} }
while (dur) { while (dur) {
/* take any loop into account. we can't read past the end of the loop. */ /* take any loop into account. we can't read past the end of the loop. */
if (loc && (loop_end - start <= dur)) { if (loc && !reversed) {
this_read = loop_end - start;
// cerr << "reloop true: thisread: " << this_read << " dur: " << dur << endl; if (!loop_range) {
reloop = true; loop_range = new Evoral::Range<framepos_t> (loop_start, loop_end-1); // inclusive semantics require -1
}
/* if we are (seamlessly) looping, ensure that the first frame we read is at the correct
position within the loop.
*/
effective_start = loop_range->squish (effective_start);
if ((loop_end - effective_start) <= dur) {
/* too close to end of loop to read "dur", so
shorten it.
*/
this_read = loop_end - effective_start;
} else {
this_read = dur;
}
} else { } else {
reloop = false;
this_read = dur; this_read = dur;
} }
@ -746,9 +753,11 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
break; break;
} }
this_read = min(dur,this_read); this_read = min (dur,this_read);
if (midi_playlist()->read (*_playback_buf, start, this_read, 0, filter) != this_read) { DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS ::read at %1 for %2 loffset %3\n", effective_start, this_read, loop_offset));
if (midi_playlist()->read (*_playback_buf, effective_start, this_read, loop_range, 0, filter) != this_read) {
error << string_compose( error << string_compose(
_("MidiDiskstream %1: cannot read %2 from playlist at frame %3"), _("MidiDiskstream %1: cannot read %2 from playlist at frame %3"),
id(), this_read, start) << endmsg; id(), this_read, start) << endmsg;
@ -765,14 +774,16 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
} else { } else {
/* if we read to the end of the loop, go back to the beginning */ /* adjust passed-by-reference argument (note: this is
if (reloop) { monotonic and does not reflect looping.
// Synthesize LoopEvent here, because the next events */
// written will have non-monotonic timestamps. start += this_read;
start = loop_start;
} else { /* similarly adjust effective_start, but this may be
start += this_read; readjusted for seamless looping as we continue around
} the loop.
*/
effective_start += this_read;
} }
dur -= this_read; dur -= this_read;
@ -795,6 +806,10 @@ MidiDiskstream::do_refill ()
size_t write_space = _playback_buf->write_space(); size_t write_space = _playback_buf->write_space();
bool reversed = (_visible_speed * _session.transport_speed()) < 0.0f; bool reversed = (_visible_speed * _session.transport_speed()) < 0.0f;
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MDS refill, write space = %1 file frame = %2\n",
write_space, file_frame));
/* no space to write */
if (write_space == 0) { if (write_space == 0) {
return 0; return 0;
} }
@ -808,22 +823,15 @@ MidiDiskstream::do_refill ()
return 0; return 0;
} }
/* no space to write */
if (_playback_buf->write_space() == 0) {
return 0;
}
uint32_t frames_read = g_atomic_int_get(&_frames_read_from_ringbuffer); uint32_t frames_read = g_atomic_int_get(&_frames_read_from_ringbuffer);
uint32_t frames_written = g_atomic_int_get(&_frames_written_to_ringbuffer); uint32_t frames_written = g_atomic_int_get(&_frames_written_to_ringbuffer);
if ((frames_read < frames_written) && (frames_written - frames_read) >= midi_readahead) { if ((frames_read < frames_written) && (frames_written - frames_read) >= midi_readahead) {
return 0; return 0;
} }
framecnt_t to_read = midi_readahead - ((framecnt_t)frames_written - (framecnt_t)frames_read); framecnt_t to_read = midi_readahead - ((framecnt_t)frames_written - (framecnt_t)frames_read);
//cout << "MDS read for midi_readahead " << to_read << " rb_contains: "
// << frames_written - frames_read << endl;
to_read = min (to_read, (framecnt_t) (max_framepos - file_frame)); to_read = min (to_read, (framecnt_t) (max_framepos - file_frame));
to_read = min (to_read, (framecnt_t) write_space); to_read = min (to_read, (framecnt_t) write_space);
@ -1077,7 +1085,7 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen
_write_source->drop_references (); _write_source->drop_references ();
_write_source.reset(); _write_source.reset();
} }
} }
} }
@ -1428,25 +1436,21 @@ MidiDiskstream::get_playback (MidiBuffer& dst, framecnt_t nframes)
Location* loc = loop_location; Location* loc = loop_location;
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ( DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose (
"%1 MDS pre-read read %8 @ %4..%5 from %2 write to %3, LOOPED ? %6-%7\n", _name, "%1 MDS pre-read read %8 offset = %9 @ %4..%5 from %2 write to %3, LOOPED ? %6 .. %7\n", _name,
_playback_buf->get_read_ptr(), _playback_buf->get_write_ptr(), playback_sample, playback_sample + nframes, _playback_buf->get_read_ptr(), _playback_buf->get_write_ptr(), playback_sample, playback_sample + nframes,
(loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes)); (loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes, Port::port_offset()));
// cerr << "================\n"; //cerr << "======== PRE ========\n";
// _playback_buf->dump (cerr); //_playback_buf->dump (cerr);
// cerr << "----------------\n"; //cerr << "----------------\n";
size_t events_read = 0; size_t events_read = 0;
const size_t split_cycle_offset = Port::port_offset ();
if (loc) { if (loc) {
framepos_t effective_start; framepos_t effective_start;
if (playback_sample >= loc->end()) { Evoral::Range<framepos_t> loop_range (loc->start(), loc->end() - 1);
effective_start = loc->start() + ((playback_sample - loc->end()) % loc->length()); effective_start = loop_range.squish (playback_sample);
} else {
effective_start = playback_sample;
}
DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("looped, effective start adjusted to %1\n", effective_start)); DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("looped, effective start adjusted to %1\n", effective_start));
@ -1505,6 +1509,10 @@ MidiDiskstream::get_playback (MidiBuffer& dst, framecnt_t nframes)
_playback_buf->get_read_ptr(), _playback_buf->get_write_ptr())); _playback_buf->get_read_ptr(), _playback_buf->get_write_ptr()));
g_atomic_int_add (&_frames_read_from_ringbuffer, nframes); g_atomic_int_add (&_frames_read_from_ringbuffer, nframes);
//cerr << "======== POST ========\n";
//_playback_buf->dump (cerr);
//cerr << "----------------\n";
} }
bool bool

View File

@ -110,6 +110,7 @@ framecnt_t
MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst, MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst,
framepos_t start, framepos_t start,
framecnt_t dur, framecnt_t dur,
Evoral::Range<framepos_t>* loop_range,
unsigned chan_n, unsigned chan_n,
MidiChannelFilter* filter) MidiChannelFilter* filter)
{ {
@ -190,7 +191,11 @@ MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst,
} }
/* Read from region into target. */ /* Read from region into target. */
mr->read_at (tgt, start, dur, chan_n, _note_mode, &tracker->tracker, filter); DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("read from %1 at %2 for %3 LR %4 .. %5\n",
mr->name(), start, dur,
(loop_range ? loop_range->from : -1),
(loop_range ? loop_range->to : -1)));
mr->read_at (tgt, start, dur, loop_range, chan_n, _note_mode, &tracker->tracker, filter);
DEBUG_TRACE (DEBUG::MidiPlaylistIO, DEBUG_TRACE (DEBUG::MidiPlaylistIO,
string_compose ("\tPost-read: %1 active notes\n", tracker->tracker.on())); string_compose ("\tPost-read: %1 active notes\n", tracker->tracker.on()));
@ -202,7 +207,7 @@ MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst,
string_compose ("\t%1 ended, resolve notes and delete (%2) tracker\n", string_compose ("\t%1 ended, resolve notes and delete (%2) tracker\n",
mr->name(), ((new_tracker) ? "new" : "old"))); mr->name(), ((new_tracker) ? "new" : "old")));
tracker->tracker.resolve_notes (tgt, (*i)->last_frame()); tracker->tracker.resolve_notes (tgt, loop_range ? loop_range->squish ((*i)->last_frame()) : (*i)->last_frame());
if (!new_tracker) { if (!new_tracker) {
_note_trackers.erase (t); _note_trackers.erase (t);
} }

View File

@ -125,7 +125,9 @@ framecnt_t
MidiPlaylistSource::read_unlocked (const Lock& lock, MidiPlaylistSource::read_unlocked (const Lock& lock,
Evoral::EventSink<framepos_t>& dst, Evoral::EventSink<framepos_t>& dst,
framepos_t /*position*/, framepos_t /*position*/,
framepos_t start, framecnt_t cnt, framepos_t start,
framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
MidiStateTracker*, MidiStateTracker*,
MidiChannelFilter*) const MidiChannelFilter*) const
{ {
@ -135,7 +137,7 @@ MidiPlaylistSource::read_unlocked (const Lock& lock,
return 0; return 0;
} }
return mp->read (dst, start, cnt); return mp->read (dst, start, cnt, loop_range);
} }
framecnt_t framecnt_t

View File

@ -257,7 +257,7 @@ MidiRegion::update_after_tempo_map_change (bool /* send */)
/* /*
set _start to new position in tempo map. set _start to new position in tempo map.
The user probably expects the region contents to maintain audio position as the The user probably expects the region contents to maintain audio position as the
tempo changes, but AFAICT this requires modifying the src file to use tempo changes, but AFAICT this requires modifying the src file to use
SMPTE timestamps with the current disk read model (?). SMPTE timestamps with the current disk read model (?).
@ -334,18 +334,24 @@ framecnt_t
MidiRegion::read_at (Evoral::EventSink<framepos_t>& out, MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
framepos_t position, framepos_t position,
framecnt_t dur, framecnt_t dur,
Evoral::Range<framepos_t>* loop_range,
uint32_t chan_n, uint32_t chan_n,
NoteMode mode, NoteMode mode,
MidiStateTracker* tracker, MidiStateTracker* tracker,
MidiChannelFilter* filter) const MidiChannelFilter* filter) const
{ {
return _read_at (_sources, out, position, dur, chan_n, mode, tracker, filter); return _read_at (_sources, out, position, dur, loop_range, chan_n, mode, tracker, filter);
} }
framecnt_t framecnt_t
MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out, framepos_t position, framecnt_t dur, uint32_t chan_n, NoteMode mode) const MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
framepos_t position,
framecnt_t dur,
Evoral::Range<framepos_t>* loop_range,
uint32_t chan_n,
NoteMode mode) const
{ {
return _read_at (_master_sources, out, position, dur, chan_n, mode); /* no tracker */ return _read_at (_master_sources, out, position, dur, loop_range, chan_n, mode); /* no tracker */
} }
framecnt_t framecnt_t
@ -353,6 +359,7 @@ MidiRegion::_read_at (const SourceList& /*srcs*/,
Evoral::EventSink<framepos_t>& dst, Evoral::EventSink<framepos_t>& dst,
framepos_t position, framepos_t position,
framecnt_t dur, framecnt_t dur,
Evoral::Range<framepos_t>* loop_range,
uint32_t chan_n, uint32_t chan_n,
NoteMode mode, NoteMode mode,
MidiStateTracker* tracker, MidiStateTracker* tracker,
@ -392,30 +399,34 @@ MidiRegion::_read_at (const SourceList& /*srcs*/,
src->set_note_mode(lm, mode); src->set_note_mode(lm, mode);
/* #if 0
cerr << "MR " << name () << " read @ " << position << " * " << to_read cerr << "MR " << name () << " read @ " << position << " + " << to_read
<< " _position = " << _position << " dur was " << dur
<< " _start = " << _start << " len " << _length
<< " intoffset = " << internal_offset << " l-io " << (_length - internal_offset)
<< " pulse = " << pulse() << " _position = " << _position
<< " start_pulse = " << start_pulse() << " _start = " << _start
<< " start_beat = " << _start_beats << " intoffset = " << internal_offset
<< endl; << " pulse = " << pulse()
*/ << " start_pulse = " << start_pulse()
<< " start_beat = " << _start_beats
<< endl;
#endif
/* This call reads events from a source and writes them to `dst' timed in session frames */ /* This call reads events from a source and writes them to `dst' timed in session frames */
if (src->midi_read ( if (src->midi_read (
lm, // source lock lm, // source lock
dst, // destination buffer dst, // destination buffer
_position - _start, // start position of the source in session frames _position - _start, // start position of the source in session frames
_start + internal_offset, // where to start reading in the source _start + internal_offset, // where to start reading in the source
to_read, // read duration in frames to_read, // read duration in frames
tracker, loop_range,
filter, tracker,
_filtered_parameters, filter,
pulse(), _filtered_parameters,
start_pulse() pulse(),
start_pulse()
) != to_read) { ) != to_read) {
return 0; /* "read nothing" */ return 0; /* "read nothing" */
} }

View File

@ -193,6 +193,7 @@ MidiSource::midi_read (const Lock& lm,
framepos_t source_start, framepos_t source_start,
framepos_t start, framepos_t start,
framecnt_t cnt, framecnt_t cnt,
Evoral::Range<framepos_t>* loop_range,
MidiStateTracker* tracker, MidiStateTracker* tracker,
MidiChannelFilter* filter, MidiChannelFilter* filter,
const std::set<Evoral::Parameter>& filtered, const std::set<Evoral::Parameter>& filtered,
@ -203,102 +204,135 @@ MidiSource::midi_read (const Lock& lm,
const int32_t tpb = Timecode::BBT_Time::ticks_per_beat; const int32_t tpb = Timecode::BBT_Time::ticks_per_beat;
const double pulse_tick_res = floor ((pulse * 4.0 * tpb) + 0.5) / tpb; const double pulse_tick_res = floor ((pulse * 4.0 * tpb) + 0.5) / tpb;
const double start_qn = (pulse - start_pulse) * 4.0; const double start_qn = (pulse - start_pulse) * 4.0;
DEBUG_TRACE (DEBUG::MidiSourceIO, DEBUG_TRACE (DEBUG::MidiSourceIO,
string_compose ("MidiSource::midi_read() %5 sstart %1 start %2 cnt %3 tracker %4\n", string_compose ("MidiSource::midi_read() %5 sstart %1 start %2 cnt %3 tracker %4\n",
source_start, start, cnt, tracker, name())); source_start, start, cnt, tracker, name()));
if (_model) { if (!_model) {
// Find appropriate model iterator return read_unlocked (lm, dst, source_start, start, cnt, loop_range, tracker, filter);
Evoral::Sequence<Evoral::Beats>::const_iterator& i = _model_iter; }
const bool linear_read = _last_read_end != 0 && start == _last_read_end;
if (!linear_read || !_model_iter_valid) { // Find appropriate model iterator
Evoral::Sequence<Evoral::Beats>::const_iterator& i = _model_iter;
const bool linear_read = _last_read_end != 0 && start == _last_read_end;
if (!linear_read || !_model_iter_valid) {
#if 0 #if 0
// Cached iterator is invalid, search for the first event past start // Cached iterator is invalid, search for the first event past start
i = _model->begin(converter.from(start), false, filtered, i = _model->begin(converter.from(start), false, filtered,
linear_read ? &_model->active_notes() : NULL); linear_read ? &_model->active_notes() : NULL);
_model_iter_valid = true; _model_iter_valid = true;
if (!linear_read) { if (!linear_read) {
_model->active_notes().clear(); _model->active_notes().clear();
}
#else
/* hot-fix http://tracker.ardour.org/view.php?id=6541
* "parallel playback of linked midi regions -> no note-offs"
*
* A midi source can be used by multiple tracks simultaneously,
* in which case midi_read() may be called from different tracks for
* overlapping time-ranges.
*
* However there is only a single iterator for a given midi-source.
* This results in every midi_read() performing a seek.
*
* If seeking is performed with
* _model->begin(converter.from(start),...)
* the model is used for seeking. That method seeks to the first
* *note-on* event after 'start'.
*
* _model->begin(converter.from( ) ,..) eventually calls
* Sequence<Time>::const_iterator() in libs/evoral/src/Sequence.cpp
* which looks up the note-event via seq.note_lower_bound(t);
* but the sequence 'seq' only contains note-on events(!).
* note-off events are implicit in Sequence<Time>::operator++()
* via _active_notes.pop(); and not part of seq.
*
* see also http://tracker.ardour.org/view.php?id=6287#c16671
*
* The linear search below assures that reading starts at the first
* event for the given time, regardless of its event-type.
*
* The performance of this approach is O(N), while the previous
* implementation is O(log(N)). This needs to be optimized:
* The model-iterator or event-sequence needs to be re-designed in
* some way (maybe keep an iterator per playlist).
*/
for (i = _model->begin(); i != _model->end(); ++i) {
if (floor (((i->time().to_double() + start_qn) * tpb) + 0.5) / tpb >= pulse_tick_res) {
break;
}
}
_model_iter_valid = true;
if (!linear_read) {
_model->active_notes().clear();
}
#endif
} }
#else
_last_read_end = start + cnt; /* hot-fix http://tracker.ardour.org/view.php?id=6541
* "parallel playback of linked midi regions -> no note-offs"
// Copy events in [start, start + cnt) into dst *
for (; i != _model->end(); ++i) { * A midi source can be used by multiple tracks simultaneously,
* in which case midi_read() may be called from different tracks for
const framecnt_t time_frames = _session.tempo_map().frame_at_quarter_note (i->time().to_double() + start_qn); * overlapping time-ranges.
if (time_frames < start + cnt + source_start) { *
if (filter && filter->filter(i->buffer(), i->size())) { * However there is only a single iterator for a given midi-source.
DEBUG_TRACE (DEBUG::MidiSourceIO, * This results in every midi_read() performing a seek.
string_compose ("%1: filter event @ %2 type %3 size %4\n", *
_name, time_frames, i->event_type(), i->size())); * If seeking is performed with
continue; * _model->begin(converter.from(start),...)
} * the model is used for seeking. That method seeks to the first
// Offset by source start to convert event time to session time * *note-on* event after 'start'.
dst.write (time_frames, i->event_type(), i->size(), i->buffer()); *
* _model->begin(converter.from( ) ,..) eventually calls
DEBUG_TRACE (DEBUG::MidiSourceIO, * Sequence<Time>::const_iterator() in libs/evoral/src/Sequence.cpp
string_compose ("%1: add event @ %2 type %3 size %4\n", * which looks up the note-event via seq.note_lower_bound(t);
_name, time_frames, i->event_type(), i->size())); * but the sequence 'seq' only contains note-on events(!).
* note-off events are implicit in Sequence<Time>::operator++()
if (tracker) { * via _active_notes.pop(); and not part of seq.
tracker->track (*i); *
} * see also http://tracker.ardour.org/view.php?id=6287#c16671
} else { *
DEBUG_TRACE (DEBUG::MidiSourceIO, * The linear search below assures that reading starts at the first
string_compose ("%1: reached end with event @ %2 vs. %3\n", * event for the given time, regardless of its event-type.
_name, time_frames, start+cnt)); *
* The performance of this approach is O(N), while the previous
* implementation is O(log(N)). This needs to be optimized:
* The model-iterator or event-sequence needs to be re-designed in
* some way (maybe keep an iterator per playlist).
*/
for (i = _model->begin(); i != _model->end(); ++i) {
if (floor (((i->time().to_double() + start_qn) * tpb) + 0.5) / tpb >= pulse_tick_res) {
break; break;
} }
} }
return cnt; _model_iter_valid = true;
} else { if (!linear_read) {
return read_unlocked (lm, dst, source_start, start, cnt, tracker, filter); _model->active_notes().clear();
}
#endif
} }
_last_read_end = start + cnt;
// Copy events in [start, start + cnt) into dst
for (; i != _model->end(); ++i) {
// Offset by source start to convert event time to session time
framecnt_t time_frames = _session.tempo_map().frame_at_quarter_note (i->time().to_double() + start_qn);
if (time_frames < (start + source_start)) {
/* event too early */
continue;
} else if (time_frames >= start + cnt + source_start) {
DEBUG_TRACE (DEBUG::MidiSourceIO,
string_compose ("%1: reached end with event @ %2 vs. %3\n",
_name, time_frames, start+cnt));
break;
} else {
/* in range */
if (filter && filter->filter(i->buffer(), i->size())) {
DEBUG_TRACE (DEBUG::MidiSourceIO,
string_compose ("%1: filter event @ %2 type %3 size %4\n",
_name, time_frames, i->event_type(), i->size()));
continue;
}
if (loop_range) {
time_frames = loop_range->squish (time_frames);
}
dst.write (time_frames, i->event_type(), i->size(), i->buffer());
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::MidiSourceIO)) {
DEBUG_STR_DECL(a);
DEBUG_STR_APPEND(a, string_compose ("%1 added event @ %2 sz %3 within %4 .. %5\n",
_name, time_frames, i->size(),
start + source_start, start + cnt + source_start));
for (size_t n=0; n < i->size(); ++n) {
DEBUG_STR_APPEND(a,hex);
DEBUG_STR_APPEND(a,"0x");
DEBUG_STR_APPEND(a,(int)i->buffer()[n]);
DEBUG_STR_APPEND(a,' ');
}
DEBUG_STR_APPEND(a,'\n');
DEBUG_TRACE (DEBUG::MidiSourceIO, DEBUG_STR(a).str());
}
#endif
if (tracker) {
tracker->track (*i);
}
}
}
return cnt;
} }
framecnt_t framecnt_t

View File

@ -377,7 +377,7 @@ Plugin::resolve_midi ()
*/ */
_pending_stop_events.get_midi(0).clear (); _pending_stop_events.get_midi(0).clear ();
_tracker.resolve_notes (_pending_stop_events.get_midi (0), /* split cycle offset*/ Port::port_offset()); _tracker.resolve_notes (_pending_stop_events.get_midi (0), 0);
_have_pending_stop_events = true; _have_pending_stop_events = true;
} }

View File

@ -215,6 +215,7 @@ SMFSource::read_unlocked (const Lock& lock,
framepos_t const source_start, framepos_t const source_start,
framepos_t start, framepos_t start,
framecnt_t duration, framecnt_t duration,
Evoral::Range<framepos_t>* loop_range,
MidiStateTracker* tracker, MidiStateTracker* tracker,
MidiChannelFilter* filter) const MidiChannelFilter* filter) const
{ {
@ -288,6 +289,10 @@ SMFSource::read_unlocked (const Lock& lock,
*/ */
const framepos_t ev_frame_time = converter.to(Evoral::Beats::ticks_at_rate(time, ppqn())) + source_start; const framepos_t ev_frame_time = converter.to(Evoral::Beats::ticks_at_rate(time, ppqn())) + source_start;
if (loop_range) {
loop_range->squish (ev_frame_time);
}
if (ev_frame_time < start + duration) { if (ev_frame_time < start + duration) {
if (!filter || !filter->filter(ev_buffer, ev_size)) { if (!filter || !filter->filter(ev_buffer, ev_size)) {
destination.write (ev_frame_time, ev_type, ev_size, ev_buffer); destination.write (ev_frame_time, ev_type, ev_size, ev_buffer);
@ -793,5 +798,3 @@ SMFSource::prevent_deletion ()
_flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy)); _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
} }