Fixes 7004 - Ardour crash on Just Quit when AudioEngine is stopped. AudioEngine:...
[ardour.git] / libs / ardour / midi_diskstream.cc
index 229c0ad7688aea940eec7fd8042dcd5bb2e901df..708884900ca48c2880bac091a1b66cb1433505a3 100644 (file)
@@ -16,7 +16,6 @@
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
-#include <fstream>
 #include <cstdio>
 #include <unistd.h>
 #include <cmath>
@@ -59,7 +58,7 @@
 
 #include "midi++/types.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 #include <locale.h>
 
 using namespace std;
@@ -361,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;
 
@@ -458,7 +458,7 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t
                                   that it can read it if it likes.
                                */
                                _gui_feed_buffer.clear ();
-                               
+
                                for (MidiBuffer::iterator i = buf.begin(); i != buf.end(); ++i) {
                                        /* This may fail if buf is larger than _gui_feed_buffer, but it's not really
                                           the end of the world if it does.
@@ -507,9 +507,9 @@ 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));
                get_playback (mbuf, playback_distance);
 
@@ -583,7 +583,7 @@ MidiDiskstream::commit (framecnt_t playback_distance)
         * but we do need to check so that the decision on whether or not we
         * need the butler is done correctly.
         */
-       
+
        /* furthermore..
         *
         * Doing heavy GUI operations[1] can stall also the butler.
@@ -704,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) {
-
-               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.
-               */
+       frameoffset_t loop_offset = 0;
 
-               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;
                }
 
@@ -747,15 +753,17 @@ 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;
                        return -1;
                }
-               
+
                g_atomic_int_add (&_frames_written_to_ringbuffer, this_read);
 
                if (reversed) {
@@ -766,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;
@@ -796,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;
        }
@@ -809,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);
 
@@ -857,8 +864,8 @@ MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush)
 
        const framecnt_t total = g_atomic_int_get(const_cast<gint*> (&_frames_pending_write));
 
-       if (total == 0 || 
-           _capture_buf->read_space() == 0 || 
+       if (total == 0 ||
+           _capture_buf->read_space() == 0 ||
            (!force_flush && (total < disk_write_chunk_frames) && was_recording)) {
                goto out;
        }
@@ -890,7 +897,7 @@ MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush)
                if (_write_source->midi_write (lm, *_capture_buf, get_capture_start_frame (0), to_write) != to_write) {
                        error << string_compose(_("MidiDiskstream %1: cannot write to disk"), id()) << endmsg;
                        return -1;
-               } 
+               }
                g_atomic_int_add(const_cast<gint*> (&_frames_pending_write), -to_write);
        }
 
@@ -1078,7 +1085,7 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen
                                _write_source->drop_references ();
                                _write_source.reset();
                        }
-               }
+               }
 
        }
 
@@ -1157,7 +1164,7 @@ MidiDiskstream::set_record_enabled (bool yn)
                } else {
                        disengage_record_enable ();
                }
-               
+
                RecordEnableChanged (); /* EMIT SIGNAL */
        }
 }
@@ -1168,18 +1175,18 @@ MidiDiskstream::set_record_safe (bool yn)
        if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_midi() == 0) { // REQUIRES REVIEW
                return;
        }
-       
+
        /* yes, i know that this not proof against race conditions, but its
         good enough. i think.
         */
-       
+
        if (record_safe () != yn) {
                if (yn) {
                        engage_record_safe ();
                } else {
                        disengage_record_safe ();
                }
-               
+
                RecordSafeChanged (); /* EMIT SIGNAL */
        }
 }
@@ -1194,7 +1201,7 @@ MidiDiskstream::prep_record_enable ()
        bool const rolling = _session.transport_speed() != 0.0f;
 
        boost::shared_ptr<MidiPort> sp = _source_port.lock ();
-       
+
        if (sp && Config->get_monitoring_model() == HardwareMonitoring) {
                sp->request_input_monitoring (!(_session.config.get_auto_input() && rolling));
        }
@@ -1214,7 +1221,7 @@ MidiDiskstream::get_state ()
 {
        XMLNode& node (Diskstream::get_state());
        char buf[64];
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
 
        if (_write_source && _session.get_record_enabled()) {
 
@@ -1248,7 +1255,7 @@ MidiDiskstream::set_state (const XMLNode& node, int version)
        XMLNodeList nlist = node.children();
        XMLNodeIterator niter;
        XMLNode* capture_pending_node = 0;
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
 
        /* prevent write sources from being created */
 
@@ -1321,14 +1328,14 @@ MidiDiskstream::steal_write_source_name ()
 
        try {
                string new_path = _session.new_midi_source_path (name());
-               
+
                if (_write_source->rename (new_path)) {
                        return string();
                }
        } catch (...) {
                return string ();
        }
-       
+
        return our_old_name;
 }
 
@@ -1360,7 +1367,7 @@ void
 MidiDiskstream::ensure_input_monitoring (bool yn)
 {
        boost::shared_ptr<MidiPort> sp = _source_port.lock ();
-       
+
        if (sp) {
                sp->ensure_input_monitoring (yn);
        }
@@ -1385,7 +1392,7 @@ float
 MidiDiskstream::playback_buffer_load () const
 {
        /* For MIDI it's not trivial to differentiate the following two cases:
-          
+
           1.  The playback buffer is empty because the system has run out of time to fill it.
           2.  The playback buffer is empty because there is no more data on the playlist.
 
@@ -1393,7 +1400,7 @@ MidiDiskstream::playback_buffer_load () const
           cannot keep up when #2 happens, when in fact it can.  Since MIDI data rates
           are so low compared to audio, just give a pretend answer here.
        */
-       
+
        return 1;
 }
 
@@ -1401,7 +1408,7 @@ float
 MidiDiskstream::capture_buffer_load () const
 {
        /* We don't report playback buffer load, so don't report capture load either */
-       
+
        return 1;
 }
 
@@ -1429,25 +1436,22 @@ 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,
-                            _playback_buf->get_read_ptr(), _playback_buf->get_write_ptr(), playback_sample, playback_sample + nframes, 
-                            (loc ? loc->start() : -1), (loc ? loc->end() : -1), nframes));
+                            "%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, 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; 
+       size_t events_read = 0;
 
        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));
 
                if (effective_start == loc->start()) {
@@ -1458,33 +1462,35 @@ MidiDiskstream::get_playback (MidiBuffer& dst, framecnt_t nframes)
                        _playback_buf->resolve_tracker (dst, 0);
                }
 
-               _playback_buf->skip_to (effective_start);
+               /* for split-cycles we need to offset the events */
 
                if (loc->end() >= effective_start && loc->end() < effective_start + nframes) {
+
                        /* end of loop is within the range we are reading, so
                           split the read in two, and lie about the location
                           for the 2nd read
                        */
+
                        framecnt_t first, second;
 
                        first = loc->end() - effective_start;
                        second = nframes - first;
 
-                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read for eff %1 end %2: %3 and %4\n",
-                                                                             effective_start, loc->end(), first, second));
+                       DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read for eff %1 end %2: %3 and %4, cycle offset %5\n",
+                                                                             effective_start, loc->end(), first, second));
 
                        if (first) {
                                DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #1, from %1 for %2\n",
                                                                                      effective_start, first));
                                events_read = _playback_buf->read (dst, effective_start, first);
-                       } 
+                       }
 
                        if (second) {
                                DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #2, from %1 for %2\n",
                                                                                      loc->start(), second));
                                events_read += _playback_buf->read (dst, loc->start(), second);
                        }
-                                                                   
+
                } else {
                        DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #3, adjusted start as %1 for %2\n",
                                                                              effective_start, nframes));
@@ -1492,6 +1498,7 @@ MidiDiskstream::get_playback (MidiBuffer& dst, framecnt_t nframes)
                }
        } else {
                _playback_buf->skip_to (playback_sample);
+               DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("playback buffer read, from %1 to %2 (%3)", playback_sample, playback_sample + nframes, nframes));
                events_read = _playback_buf->read (dst, playback_sample, playback_sample + nframes);
        }
 
@@ -1502,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
@@ -1535,7 +1546,7 @@ boost::shared_ptr<MidiBuffer>
 MidiDiskstream::get_gui_feed_buffer () const
 {
        boost::shared_ptr<MidiBuffer> b (new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI)));
-       
+
        Glib::Threads::Mutex::Lock lm (_gui_feed_buffer_mutex);
        b->copy (_gui_feed_buffer);
        return b;