change all MIDI read-from-source to map all events into the loop-range for seamless looping (if using)
This commit is contained in:
parent
182e35235c
commit
f41bc70ee9
|
@ -70,6 +70,7 @@ public:
|
|||
* @param buf Destination for events.
|
||||
* @param start First frame of 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
|
||||
* channel is backed by a separate region, not MIDI channels, which all
|
||||
* exist in the same region and are not handled here).
|
||||
|
@ -78,6 +79,7 @@ public:
|
|||
framecnt_t read (Evoral::EventSink<framepos_t>& buf,
|
||||
framepos_t start,
|
||||
framecnt_t cnt,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
uint32_t chan_n = 0,
|
||||
MidiChannelFilter* filter = NULL);
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ protected:
|
|||
framepos_t position,
|
||||
framepos_t start,
|
||||
framecnt_t cnt,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
MidiStateTracker* tracker,
|
||||
MidiChannelFilter* filter) const;
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "evoral/Beats.hpp"
|
||||
#include "evoral/Range.hpp"
|
||||
|
||||
#include "ardour/ardour.h"
|
||||
#include "ardour/region.h"
|
||||
|
@ -75,6 +76,7 @@ class LIBARDOUR_API MidiRegion : public Region
|
|||
framecnt_t read_at (Evoral::EventSink<framepos_t>& dst,
|
||||
framepos_t position,
|
||||
framecnt_t dur,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
uint32_t chan_n = 0,
|
||||
NoteMode mode = Sustained,
|
||||
MidiStateTracker* tracker = 0,
|
||||
|
@ -83,6 +85,7 @@ class LIBARDOUR_API MidiRegion : public Region
|
|||
framecnt_t master_read_at (MidiRingBuffer<framepos_t>& dst,
|
||||
framepos_t position,
|
||||
framecnt_t dur,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
uint32_t chan_n = 0,
|
||||
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,
|
||||
framepos_t position,
|
||||
framecnt_t dur,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
uint32_t chan_n = 0,
|
||||
NoteMode mode = Sustained,
|
||||
MidiStateTracker* tracker = 0,
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "pbd/stateful.h"
|
||||
#include "pbd/xml++.h"
|
||||
#include "evoral/Sequence.hpp"
|
||||
#include "evoral/Range.hpp"
|
||||
#include "ardour/ardour.h"
|
||||
#include "ardour/buffer.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 start Start of range to be read.
|
||||
* \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 filtered Parameters whose MIDI messages will not be returned.
|
||||
*/
|
||||
|
||||
virtual framecnt_t midi_read (const Lock& lock,
|
||||
Evoral::EventSink<framepos_t>& dst,
|
||||
framepos_t source_start,
|
||||
framepos_t start,
|
||||
framecnt_t cnt,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
MidiStateTracker* tracker,
|
||||
MidiChannelFilter* filter,
|
||||
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 start,
|
||||
framecnt_t cnt,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
MidiStateTracker* tracker,
|
||||
MidiChannelFilter* filter) const = 0;
|
||||
|
||||
|
|
|
@ -94,6 +94,7 @@ public:
|
|||
framepos_t position,
|
||||
framepos_t start,
|
||||
framecnt_t cnt,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
MidiStateTracker* tracker,
|
||||
MidiChannelFilter* filter) const;
|
||||
|
||||
|
|
|
@ -360,7 +360,8 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
|
|||
framepos_t loop_start = 0;
|
||||
framepos_t loop_end = 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;
|
||||
|
||||
|
@ -506,7 +507,7 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
|
|||
playback_distance = nframes;
|
||||
}
|
||||
|
||||
if (need_disk_signal) {
|
||||
if (need_disk_signal && !_session.declick_out_pending()) {
|
||||
/* copy the diskstream data to all output buffers */
|
||||
|
||||
MidiBuffer& mbuf (bufs.get_midi (0));
|
||||
|
@ -703,42 +704,48 @@ int
|
|||
MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
|
||||
{
|
||||
framecnt_t this_read = 0;
|
||||
bool reloop = false;
|
||||
framepos_t loop_end = 0;
|
||||
framepos_t loop_start = 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);
|
||||
MidiChannelFilter* filter = mt ? &mt->playback_filter() : NULL;
|
||||
|
||||
if (!reversed) {
|
||||
frameoffset_t loop_offset = 0;
|
||||
|
||||
loc = loop_location;
|
||||
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;
|
||||
if (!reversed && loc) {
|
||||
get_location_times (loc, &loop_start, &loop_end, &loop_length);
|
||||
}
|
||||
|
||||
while (dur) {
|
||||
|
||||
/* take any loop into account. we can't read past the end of the loop. */
|
||||
|
||||
if (loc && (loop_end - start <= dur)) {
|
||||
this_read = loop_end - start;
|
||||
// cerr << "reloop true: thisread: " << this_read << " dur: " << dur << endl;
|
||||
reloop = true;
|
||||
if (loc && !reversed) {
|
||||
|
||||
if (!loop_range) {
|
||||
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 {
|
||||
reloop = false;
|
||||
this_read = dur;
|
||||
}
|
||||
|
||||
|
@ -746,9 +753,11 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
|
|||
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(
|
||||
_("MidiDiskstream %1: cannot read %2 from playlist at frame %3"),
|
||||
id(), this_read, start) << endmsg;
|
||||
|
@ -765,14 +774,16 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed)
|
|||
|
||||
} else {
|
||||
|
||||
/* if we read to the end of the loop, go back to the beginning */
|
||||
if (reloop) {
|
||||
// Synthesize LoopEvent here, because the next events
|
||||
// written will have non-monotonic timestamps.
|
||||
start = loop_start;
|
||||
} else {
|
||||
start += this_read;
|
||||
}
|
||||
/* adjust passed-by-reference argument (note: this is
|
||||
monotonic and does not reflect looping.
|
||||
*/
|
||||
start += this_read;
|
||||
|
||||
/* similarly adjust effective_start, but this may be
|
||||
readjusted for seamless looping as we continue around
|
||||
the loop.
|
||||
*/
|
||||
effective_start += this_read;
|
||||
}
|
||||
|
||||
dur -= this_read;
|
||||
|
@ -795,6 +806,10 @@ MidiDiskstream::do_refill ()
|
|||
size_t write_space = _playback_buf->write_space();
|
||||
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) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -808,22 +823,15 @@ MidiDiskstream::do_refill ()
|
|||
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_written = g_atomic_int_get(&_frames_written_to_ringbuffer);
|
||||
|
||||
if ((frames_read < frames_written) && (frames_written - frames_read) >= midi_readahead) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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) write_space);
|
||||
|
||||
|
@ -1077,7 +1085,7 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen
|
|||
_write_source->drop_references ();
|
||||
_write_source.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1428,25 +1436,21 @@ MidiDiskstream::get_playback (MidiBuffer& dst, framecnt_t nframes)
|
|||
Location* loc = loop_location;
|
||||
|
||||
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,
|
||||
(loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes));
|
||||
(loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes, Port::port_offset()));
|
||||
|
||||
// cerr << "================\n";
|
||||
// _playback_buf->dump (cerr);
|
||||
// cerr << "----------------\n";
|
||||
//cerr << "======== PRE ========\n";
|
||||
//_playback_buf->dump (cerr);
|
||||
//cerr << "----------------\n";
|
||||
|
||||
size_t events_read = 0;
|
||||
const size_t split_cycle_offset = Port::port_offset ();
|
||||
|
||||
if (loc) {
|
||||
framepos_t effective_start;
|
||||
|
||||
if (playback_sample >= loc->end()) {
|
||||
effective_start = loc->start() + ((playback_sample - loc->end()) % loc->length());
|
||||
} else {
|
||||
effective_start = playback_sample;
|
||||
}
|
||||
Evoral::Range<framepos_t> loop_range (loc->start(), loc->end() - 1);
|
||||
effective_start = loop_range.squish (playback_sample);
|
||||
|
||||
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()));
|
||||
|
||||
g_atomic_int_add (&_frames_read_from_ringbuffer, nframes);
|
||||
|
||||
//cerr << "======== POST ========\n";
|
||||
//_playback_buf->dump (cerr);
|
||||
//cerr << "----------------\n";
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -110,6 +110,7 @@ framecnt_t
|
|||
MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst,
|
||||
framepos_t start,
|
||||
framecnt_t dur,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
unsigned chan_n,
|
||||
MidiChannelFilter* filter)
|
||||
{
|
||||
|
@ -190,7 +191,11 @@ MidiPlaylist::read (Evoral::EventSink<framepos_t>& dst,
|
|||
}
|
||||
|
||||
/* 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,
|
||||
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",
|
||||
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) {
|
||||
_note_trackers.erase (t);
|
||||
}
|
||||
|
|
|
@ -125,7 +125,9 @@ framecnt_t
|
|||
MidiPlaylistSource::read_unlocked (const Lock& lock,
|
||||
Evoral::EventSink<framepos_t>& dst,
|
||||
framepos_t /*position*/,
|
||||
framepos_t start, framecnt_t cnt,
|
||||
framepos_t start,
|
||||
framecnt_t cnt,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
MidiStateTracker*,
|
||||
MidiChannelFilter*) const
|
||||
{
|
||||
|
@ -135,7 +137,7 @@ MidiPlaylistSource::read_unlocked (const Lock& lock,
|
|||
return 0;
|
||||
}
|
||||
|
||||
return mp->read (dst, start, cnt);
|
||||
return mp->read (dst, start, cnt, loop_range);
|
||||
}
|
||||
|
||||
framecnt_t
|
||||
|
|
|
@ -334,18 +334,24 @@ framecnt_t
|
|||
MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
|
||||
framepos_t position,
|
||||
framecnt_t dur,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
uint32_t chan_n,
|
||||
NoteMode mode,
|
||||
MidiStateTracker* tracker,
|
||||
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
|
||||
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
|
||||
|
@ -353,6 +359,7 @@ MidiRegion::_read_at (const SourceList& /*srcs*/,
|
|||
Evoral::EventSink<framepos_t>& dst,
|
||||
framepos_t position,
|
||||
framecnt_t dur,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
uint32_t chan_n,
|
||||
NoteMode mode,
|
||||
MidiStateTracker* tracker,
|
||||
|
@ -392,30 +399,34 @@ MidiRegion::_read_at (const SourceList& /*srcs*/,
|
|||
|
||||
src->set_note_mode(lm, mode);
|
||||
|
||||
/*
|
||||
cerr << "MR " << name () << " read @ " << position << " * " << to_read
|
||||
<< " _position = " << _position
|
||||
<< " _start = " << _start
|
||||
<< " intoffset = " << internal_offset
|
||||
<< " pulse = " << pulse()
|
||||
<< " start_pulse = " << start_pulse()
|
||||
<< " start_beat = " << _start_beats
|
||||
<< endl;
|
||||
*/
|
||||
#if 0
|
||||
cerr << "MR " << name () << " read @ " << position << " + " << to_read
|
||||
<< " dur was " << dur
|
||||
<< " len " << _length
|
||||
<< " l-io " << (_length - internal_offset)
|
||||
<< " _position = " << _position
|
||||
<< " _start = " << _start
|
||||
<< " intoffset = " << internal_offset
|
||||
<< " 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 */
|
||||
|
||||
if (src->midi_read (
|
||||
lm, // source lock
|
||||
dst, // destination buffer
|
||||
_position - _start, // start position of the source in session frames
|
||||
_start + internal_offset, // where to start reading in the source
|
||||
to_read, // read duration in frames
|
||||
tracker,
|
||||
filter,
|
||||
_filtered_parameters,
|
||||
pulse(),
|
||||
start_pulse()
|
||||
dst, // destination buffer
|
||||
_position - _start, // start position of the source in session frames
|
||||
_start + internal_offset, // where to start reading in the source
|
||||
to_read, // read duration in frames
|
||||
loop_range,
|
||||
tracker,
|
||||
filter,
|
||||
_filtered_parameters,
|
||||
pulse(),
|
||||
start_pulse()
|
||||
) != to_read) {
|
||||
return 0; /* "read nothing" */
|
||||
}
|
||||
|
|
|
@ -193,6 +193,7 @@ MidiSource::midi_read (const Lock& lm,
|
|||
framepos_t source_start,
|
||||
framepos_t start,
|
||||
framecnt_t cnt,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
MidiStateTracker* tracker,
|
||||
MidiChannelFilter* filter,
|
||||
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 double pulse_tick_res = floor ((pulse * 4.0 * tpb) + 0.5) / tpb;
|
||||
const double start_qn = (pulse - start_pulse) * 4.0;
|
||||
|
||||
DEBUG_TRACE (DEBUG::MidiSourceIO,
|
||||
string_compose ("MidiSource::midi_read() %5 sstart %1 start %2 cnt %3 tracker %4\n",
|
||||
source_start, start, cnt, tracker, name()));
|
||||
|
||||
if (_model) {
|
||||
// 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 (!_model) {
|
||||
return read_unlocked (lm, dst, source_start, start, cnt, loop_range, tracker, filter);
|
||||
}
|
||||
|
||||
// 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
|
||||
// Cached iterator is invalid, search for the first event past start
|
||||
i = _model->begin(converter.from(start), false, filtered,
|
||||
linear_read ? &_model->active_notes() : NULL);
|
||||
_model_iter_valid = true;
|
||||
if (!linear_read) {
|
||||
_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
|
||||
// Cached iterator is invalid, search for the first event past start
|
||||
i = _model->begin(converter.from(start), false, filtered,
|
||||
linear_read ? &_model->active_notes() : NULL);
|
||||
_model_iter_valid = true;
|
||||
if (!linear_read) {
|
||||
_model->active_notes().clear();
|
||||
}
|
||||
|
||||
_last_read_end = start + cnt;
|
||||
|
||||
// Copy events in [start, start + cnt) into dst
|
||||
for (; i != _model->end(); ++i) {
|
||||
|
||||
const framecnt_t time_frames = _session.tempo_map().frame_at_quarter_note (i->time().to_double() + start_qn);
|
||||
if (time_frames < start + cnt + source_start) {
|
||||
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;
|
||||
}
|
||||
// Offset by source start to convert event time to session time
|
||||
dst.write (time_frames, i->event_type(), i->size(), i->buffer());
|
||||
|
||||
DEBUG_TRACE (DEBUG::MidiSourceIO,
|
||||
string_compose ("%1: add event @ %2 type %3 size %4\n",
|
||||
_name, time_frames, i->event_type(), i->size()));
|
||||
|
||||
if (tracker) {
|
||||
tracker->track (*i);
|
||||
}
|
||||
} else {
|
||||
DEBUG_TRACE (DEBUG::MidiSourceIO,
|
||||
string_compose ("%1: reached end with event @ %2 vs. %3\n",
|
||||
_name, time_frames, start+cnt));
|
||||
#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;
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
} else {
|
||||
return read_unlocked (lm, dst, source_start, start, cnt, tracker, filter);
|
||||
_model_iter_valid = true;
|
||||
if (!linear_read) {
|
||||
_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
|
||||
|
|
|
@ -377,7 +377,7 @@ Plugin::resolve_midi ()
|
|||
*/
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
|
|
|
@ -215,6 +215,7 @@ SMFSource::read_unlocked (const Lock& lock,
|
|||
framepos_t const source_start,
|
||||
framepos_t start,
|
||||
framecnt_t duration,
|
||||
Evoral::Range<framepos_t>* loop_range,
|
||||
MidiStateTracker* tracker,
|
||||
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;
|
||||
|
||||
if (loop_range) {
|
||||
loop_range->squish (ev_frame_time);
|
||||
}
|
||||
|
||||
if (ev_frame_time < start + duration) {
|
||||
if (!filter || !filter->filter(ev_buffer, ev_size)) {
|
||||
destination.write (ev_frame_time, ev_type, ev_size, ev_buffer);
|
||||
|
@ -793,5 +798,3 @@ SMFSource::prevent_deletion ()
|
|||
|
||||
_flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user