summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/ardour/ardour/midi_diskstream.h1
-rw-r--r--libs/ardour/ardour/midi_source.h34
-rw-r--r--libs/ardour/midi_diskstream.cc121
-rw-r--r--libs/ardour/midi_source.cc22
-rw-r--r--libs/ardour/smf_source.cc59
5 files changed, 139 insertions, 98 deletions
diff --git a/libs/ardour/ardour/midi_diskstream.h b/libs/ardour/ardour/midi_diskstream.h
index 45ea975ea9..be0e0fe48c 100644
--- a/libs/ardour/ardour/midi_diskstream.h
+++ b/libs/ardour/ardour/midi_diskstream.h
@@ -195,6 +195,7 @@ class MidiDiskstream : public Diskstream
volatile gint _frames_written_to_ringbuffer;
volatile gint _frames_read_from_ringbuffer;
volatile gint _frames_pending_write;
+ volatile gint _num_captured_loops;
/** A buffer that we use to put newly-arrived MIDI data in for
the GUI to read (so that it can update itself).
diff --git a/libs/ardour/ardour/midi_source.h b/libs/ardour/ardour/midi_source.h
index ed2950e9e5..a5aac8859b 100644
--- a/libs/ardour/ardour/midi_source.h
+++ b/libs/ardour/ardour/midi_source.h
@@ -88,7 +88,20 @@ class MidiSource : virtual public Source, public boost::enable_shared_from_this<
virtual void mark_streaming_midi_write_started (NoteMode mode);
virtual void mark_streaming_write_started ();
virtual void mark_streaming_write_completed ();
- void mark_write_starting_now ();
+
+ /** Mark write starting with the given time parameters.
+ *
+ * This is called by MidiDiskStream::process before writing to the capture
+ * buffer which will be later read by midi_read().
+ *
+ * @param position The timeline position the source now starts at.
+ * @param capture_length The current length of the capture, which may not
+ * be zero if record is armed while rolling.
+ * @param loop_length The loop length if looping, otherwise zero.
+ */
+ void mark_write_starting_now (framecnt_t position,
+ framecnt_t capture_length,
+ framecnt_t loop_length);
/* like ::mark_streaming_write_completed() but with more arguments to
* allow control over MIDI-specific behaviour. Expected to be used only
@@ -104,9 +117,6 @@ class MidiSource : virtual public Source, public boost::enable_shared_from_this<
std::string captured_for() const { return _captured_for; }
void set_captured_for (std::string str) { _captured_for = str; }
- framepos_t last_write_end() const { return _last_write_end; }
- void set_last_write_end (framepos_t pos) { _last_write_end = pos; }
-
static PBD::Signal1<void,MidiSource*> MidiSourceCreated;
XMLNode& get_state ();
@@ -157,11 +167,16 @@ class MidiSource : virtual public Source, public boost::enable_shared_from_this<
framecnt_t cnt,
MidiStateTracker* tracker) const = 0;
- virtual framecnt_t write_unlocked (MidiRingBuffer<framepos_t>& dst,
+ /** Write data to this source from a MidiRingBuffer.
+ * @param source Buffer to read from.
+ * @param position This source's start position in session frames.
+ * @param cnt The duration of this block to write for.
+ */
+ virtual framecnt_t write_unlocked (MidiRingBuffer<framepos_t>& source,
framepos_t position,
framecnt_t cnt) = 0;
- std::string _captured_for;
+ std::string _captured_for;
boost::shared_ptr<MidiModel> _model;
bool _writing;
@@ -171,7 +186,12 @@ class MidiSource : virtual public Source, public boost::enable_shared_from_this<
mutable double _length_beats;
mutable framepos_t _last_read_end;
- framepos_t _last_write_end;
+
+ /** The total duration of the current capture. */
+ framepos_t _capture_length;
+
+ /** Length of transport loop during current capture, or zero. */
+ framepos_t _capture_loop_length;
/** Map of interpolation styles to use for Parameters; if they are not in this map,
* the correct interpolation style can be obtained from EventTypeMap::interpolation_of ()
diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc
index 48fd3fb77f..d8429fdcd1 100644
--- a/libs/ardour/midi_diskstream.cc
+++ b/libs/ardour/midi_diskstream.cc
@@ -74,6 +74,7 @@ MidiDiskstream::MidiDiskstream (Session &sess, const string &name, Diskstream::F
, _frames_written_to_ringbuffer(0)
, _frames_read_from_ringbuffer(0)
, _frames_pending_write(0)
+ , _num_captured_loops(0)
, _gui_feed_buffer(AudioEngine::instance()->raw_buffer_size (DataType::MIDI))
{
in_set_state = true;
@@ -97,6 +98,7 @@ MidiDiskstream::MidiDiskstream (Session& sess, const XMLNode& node)
, _frames_written_to_ringbuffer(0)
, _frames_read_from_ringbuffer(0)
, _frames_pending_write(0)
+ , _num_captured_loops(0)
, _gui_feed_buffer(AudioEngine::instance()->raw_buffer_size (DataType::MIDI))
{
in_set_state = true;
@@ -200,9 +202,7 @@ MidiDiskstream::non_realtime_input_change ()
}
g_atomic_int_set(&_frames_pending_write, 0);
- if (_write_source) {
- _write_source->set_last_write_end (_session.transport_frame());
- }
+ g_atomic_int_set(&_num_captured_loops, 0);
}
int
@@ -299,6 +299,26 @@ MidiDiskstream::set_note_mode (NoteMode m)
_write_source->model()->set_note_mode(m);
}
+/** Get the start, end, and length of a location "atomically".
+ *
+ * Note: Locations don't get deleted, so all we care about when I say "atomic"
+ * is that we are always pointing to the same one and using start/length values
+ * obtained just once. Use this function to achieve this since location being
+ * a parameter achieves this.
+ */
+static void
+get_location_times(const Location* location,
+ framepos_t* start,
+ framepos_t* end,
+ framepos_t* length)
+{
+ if (location) {
+ *start = location->start();
+ *end = location->end();
+ *length = *end - *start;
+ }
+}
+
int
MidiDiskstream::process (framepos_t transport_frame, pframes_t nframes, framecnt_t& playback_distance)
{
@@ -330,6 +350,12 @@ MidiDiskstream::process (framepos_t transport_frame, pframes_t nframes, framecnt
return 1;
}
+ const Location* const loop_loc = loop_location;
+ 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);
+
adjust_capture_position = 0;
if (nominally_recording || (re && was_recording && _session.get_record_enabled() && _session.config.get_punch_in())) {
@@ -338,9 +364,19 @@ MidiDiskstream::process (framepos_t transport_frame, pframes_t nframes, framecnt
calculate_record_range(ot, transport_frame, nframes, rec_nframes, rec_offset);
if (rec_nframes && !was_recording) {
- _write_source->mark_write_starting_now ();
+ if (loop_loc) {
+ /* Loop recording, so pretend the capture started at the loop
+ start rgardless of what time it is now, so the source starts
+ at the loop start and can handle time wrapping around.
+ Otherwise, start the source right now as usual.
+ */
+ capture_captured = transport_frame - loop_start;
+ capture_start_frame = loop_start;
+ }
+ _write_source->mark_write_starting_now(
+ capture_start_frame, capture_captured, loop_length);
g_atomic_int_set(&_frames_pending_write, 0);
- capture_captured = 0;
+ g_atomic_int_set(&_num_captured_loops, 0);
was_recording = true;
}
}
@@ -370,7 +406,18 @@ MidiDiskstream::process (framepos_t transport_frame, pframes_t nframes, framecnt
DEBUG_TRACE (DEBUG::MidiIO, DEBUG_STR(a).str());
}
#endif
- _capture_buf->write(ev.time() + transport_frame, ev.type(), ev.size(), ev.buffer());
+ /* Write events to the capture buffer in frames from session start,
+ but ignoring looping so event time progresses monotonically.
+ The source knows the loop length so it knows exactly where the
+ event occurs in the series of recorded loops and can implement
+ any desirable behaviour. We don't want to send event with
+ transport time here since that way the source can not
+ reconstruct their actual time; future clever MIDI looping should
+ probabl be implemented in the source instead of here.
+ */
+ const framecnt_t loop_offset = _num_captured_loops * loop_length;
+ _capture_buf->write(transport_frame + loop_offset + ev.time(),
+ ev.type(), ev.size(), ev.buffer());
}
g_atomic_int_add(&_frames_pending_write, nframes);
@@ -551,29 +598,17 @@ MidiDiskstream::internal_playback_seek (framecnt_t distance)
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;
- Location *loc = 0;
+ 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;
if (!reversed) {
- framecnt_t loop_length = 0;
-
- /* Make the use of a Location atomic for this read operation.
-
- Note: Locations don't get deleted, so all we care about
- when I say "atomic" is that we are always pointing to
- the same one and using a start/length values obtained
- just once.
- */
-
- if ((loc = loop_location) != 0) {
- loop_start = loc->start();
- loop_end = loc->end();
- loop_length = loop_end - loop_start;
- }
+ 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.
@@ -948,33 +983,15 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen
void
MidiDiskstream::transport_looped (framepos_t transport_frame)
{
+ /* Here we only keep track of the number of captured loops so monotonic
+ event times can be delivered to the write source in process(). Trying
+ to be clever here is a world of trouble, it is better to simply record
+ the input in a straightforward non-destructive way. In the future when
+ we want to implement more clever MIDI looping modes it should be done in
+ the Source and/or entirely after the capture is finished.
+ */
if (was_recording) {
-
- // adjust the capture length knowing that the data will be recorded to disk
- // only necessary after the first loop where we're recording
- if (capture_info.size() == 0) {
- capture_captured += _capture_offset;
-
- if (_alignment_style == ExistingMaterial) {
- capture_captured += _session.worst_output_latency();
- } else {
- capture_captured += _roll_delay;
- }
- }
-
- finish_capture ();
-
- // the next region will start recording via the normal mechanism
- // we'll set the start position to the current transport pos
- // no latency adjustment or capture offset needs to be made, as that already happened the first time
- capture_start_frame = transport_frame;
- first_recordable_frame = transport_frame; // mild lie
- last_recordable_frame = max_framepos;
- was_recording = true;
- }
-
- if (!Config->get_seamless_loop()) {
- reset_tracker ();
+ g_atomic_int_add(&_num_captured_loops, 1);
}
}
diff --git a/libs/ardour/midi_source.cc b/libs/ardour/midi_source.cc
index 7ec82ed4e6..30dc23f998 100644
--- a/libs/ardour/midi_source.cc
+++ b/libs/ardour/midi_source.cc
@@ -59,7 +59,8 @@ MidiSource::MidiSource (Session& s, string name, Source::Flag flags)
, _model_iter_valid(false)
, _length_beats(0.0)
, _last_read_end(0)
- , _last_write_end(0)
+ , _capture_length(0)
+ , _capture_loop_length(0)
{
}
@@ -69,7 +70,8 @@ MidiSource::MidiSource (Session& s, const XMLNode& node)
, _model_iter_valid(false)
, _length_beats(0.0)
, _last_read_end(0)
- , _last_write_end(0)
+ , _capture_length(0)
+ , _capture_loop_length(0)
{
if (set_state (node, Stateful::loading_state_version)) {
throw failed_constructor();
@@ -266,7 +268,7 @@ MidiSource::midi_write (MidiRingBuffer<framepos_t>& source,
if (cnt == max_framecnt) {
_last_read_end = 0;
} else {
- _last_write_end += cnt;
+ _capture_length += cnt;
}
return ret;
@@ -284,10 +286,12 @@ MidiSource::mark_streaming_midi_write_started (NoteMode mode)
}
void
-MidiSource::mark_write_starting_now ()
+MidiSource::mark_write_starting_now (framecnt_t position,
+ framecnt_t capture_length,
+ framecnt_t loop_length)
{
/* I'm not sure if this is the best way to approach this, but
- _last_write_end needs to be set up with the transport frame
+ _capture_length needs to be set up with the transport frame
when a record actually starts, as it is used by
SMFSource::write_unlocked to decide whether incoming notes
are within the correct time range.
@@ -297,8 +301,12 @@ MidiSource::mark_write_starting_now ()
because it is not RT-safe.
*/
- set_timeline_position (_session.transport_frame ());
- _last_write_end = _session.transport_frame ();
+ set_timeline_position(position);
+ _capture_length = capture_length;
+ _capture_loop_length = loop_length;
+
+ BeatsFramesConverter converter(_session.tempo_map(), position);
+ _length_beats = converter.from(capture_length);
}
void
diff --git a/libs/ardour/smf_source.cc b/libs/ardour/smf_source.cc
index 2b4c7ef7c9..5b42852923 100644
--- a/libs/ardour/smf_source.cc
+++ b/libs/ardour/smf_source.cc
@@ -211,14 +211,10 @@ SMFSource::read_unlocked (Evoral::EventSink<framepos_t>& destination,
return duration;
}
-/** Write data to this source from a MidiRingBuffer.
- * @param source Buffer to read from.
- * @param position This source's start position in session frames.
- */
framecnt_t
SMFSource::write_unlocked (MidiRingBuffer<framepos_t>& source,
framepos_t position,
- framecnt_t duration)
+ framecnt_t cnt)
{
if (!_writing) {
mark_streaming_write_started ();
@@ -231,55 +227,57 @@ SMFSource::write_unlocked (MidiRingBuffer<framepos_t>& source,
size_t buf_capacity = 4;
uint8_t* buf = (uint8_t*)malloc(buf_capacity);
- if (_model && ! _model->writing()) {
+ if (_model && !_model->writing()) {
_model->start_write();
}
Evoral::MIDIEvent<framepos_t> ev;
-
- // cerr << "SMFSource::write unlocked, begins writing from src buffer with _last_write_end = " << _last_write_end << " dur = " << duration << endl;
-
while (true) {
+ /* Get the event time, in frames since session start but ignoring looping. */
bool ret;
-
if (!(ret = source.peek ((uint8_t*)&time, sizeof (time)))) {
- /* no more events to consider */
+ /* Ring is empty, no more events. */
break;
}
- if ((duration != max_framecnt) && (time > (_last_write_end + duration))) {
- DEBUG_TRACE (DEBUG::MidiIO, string_compose ("SMFSource::write_unlocked: dropping event @ %1 because it is later than %2 + %3\n",
- time, _last_write_end, duration));
+ if ((cnt != max_framecnt) &&
+ (time > position + _capture_length + cnt)) {
+ /* The diskstream doesn't want us to write everything, and this
+ event is past the end of this block, so we're done for now. */
break;
}
+ /* Read the time, type, and size of the event. */
if (!(ret = source.read_prefix (&time, &type, &size))) {
- error << _("Unable to read event prefix, corrupt MIDI ring buffer") << endmsg;
+ error << _("Unable to read event prefix, corrupt MIDI ring") << endmsg;
break;
}
+ /* Enlarge body buffer if necessary now that we know the size. */
if (size > buf_capacity) {
buf_capacity = size;
buf = (uint8_t*)realloc(buf, size);
}
+ /* Read the event body into buffer. */
ret = source.read_contents(size, buf);
if (!ret) {
- error << _("Read time/size but not buffer, corrupt MIDI ring buffer") << endmsg;
+ error << _("Event has time and size but no body, corrupt MIDI ring") << endmsg;
break;
}
- /* convert from session time to time relative to the source start */
- assert(time >= position);
+ /* Convert event time from absolute to source relative. */
+ if (time < position) {
+ error << _("Event time is before MIDI source position") << endmsg;
+ break;
+ }
time -= position;
-
+
ev.set(buf, size, time);
ev.set_event_type(EventTypeMap::instance().midi_event_type(ev.buffer()[0]));
- ev.set_id (Evoral::next_event_id());
+ ev.set_id(Evoral::next_event_id());
if (!(ev.is_channel_event() || ev.is_smf_meta_event() || ev.is_sysex())) {
- /*cerr << "SMFSource: WARNING: caller tried to write non SMF-Event of type "
- << std::hex << int(ev.buffer()[0]) << endl;*/
continue;
}
@@ -289,16 +287,14 @@ SMFSource::write_unlocked (MidiRingBuffer<framepos_t>& source,
Evoral::SMF::flush ();
free (buf);
- return duration;
+ return cnt;
}
-
/** Append an event with a timestamp in beats (double) */
void
SMFSource::append_event_unlocked_beats (const Evoral::Event<double>& ev)
{
- assert(_writing);
- if (ev.size() == 0) {
+ if (!_writing || ev.size() == 0) {
return;
}
@@ -306,9 +302,9 @@ SMFSource::append_event_unlocked_beats (const Evoral::Event<double>& ev)
name().c_str(), ev.id(), ev.time(), ev.size());
for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");*/
- assert(ev.time() >= 0);
if (ev.time() < _last_ev_time_beats) {
- cerr << "SMFSource: Warning: Skipping event with non-monotonic time" << endl;
+ warning << string_compose(_("Skipping event with unordered time %1"), ev.time())
+ << endmsg;
return;
}
@@ -337,9 +333,7 @@ SMFSource::append_event_unlocked_beats (const Evoral::Event<double>& ev)
void
SMFSource::append_event_unlocked_frames (const Evoral::Event<framepos_t>& ev, framepos_t position)
{
- assert(_writing);
- if (ev.size() == 0) {
- cerr << "SMFSource: asked to append zero-size event\n";
+ if (!_writing || ev.size() == 0) {
return;
}
@@ -348,7 +342,8 @@ SMFSource::append_event_unlocked_frames (const Evoral::Event<framepos_t>& ev, fr
// for (size_t i=0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");
if (ev.time() < _last_ev_time_frames) {
- cerr << "SMFSource: Warning: Skipping event with non-monotonic time" << endl;
+ warning << string_compose(_("Skipping event with unordered time %1"), ev.time())
+ << endmsg;
return;
}