X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fmidi_diskstream.cc;h=4d3dd4e5a7784c31cd97ec38671da2efd6ff7a14;hb=e16866416738a3b2089e760fb76d45ff7a59fe3a;hp=45b7e8f4de4af429cf2216fe2b2ac471af0874d3;hpb=89a284fd7814dcc6329c1a1aeb7e5f538bc4a679;p=ardour.git diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc index 45b7e8f4de..4d3dd4e5a7 100644 --- a/libs/ardour/midi_diskstream.cc +++ b/libs/ardour/midi_diskstream.cc @@ -16,7 +16,6 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include #include #include #include @@ -27,40 +26,39 @@ #include #include #include -#include #include "pbd/error.h" +#include "pbd/ffs.h" #include "pbd/basename.h" -#include +#include #include "pbd/xml++.h" #include "pbd/memento_command.h" #include "pbd/enumwriter.h" #include "pbd/stateful_diff_command.h" #include "pbd/stacktrace.h" -#include "ardour/ardour.h" #include "ardour/audioengine.h" #include "ardour/butler.h" -#include "ardour/configuration.h" -#include "ardour/cycle_timer.h" #include "ardour/debug.h" #include "ardour/io.h" #include "ardour/midi_diskstream.h" +#include "ardour/midi_model.h" #include "ardour/midi_playlist.h" #include "ardour/midi_port.h" #include "ardour/midi_region.h" +#include "ardour/midi_ring_buffer.h" +#include "ardour/midi_track.h" #include "ardour/playlist_factory.h" #include "ardour/region_factory.h" -#include "ardour/send.h" #include "ardour/session.h" +#include "ardour/session_playlists.h" #include "ardour/smf_source.h" +#include "ardour/types.h" #include "ardour/utils.h" -#include "ardour/session_playlists.h" -#include "ardour/route.h" #include "midi++/types.h" -#include "i18n.h" +#include "pbd/i18n.h" #include using namespace std; @@ -73,11 +71,13 @@ MidiDiskstream::MidiDiskstream (Session &sess, const string &name, Diskstream::F : Diskstream(sess, name, flag) , _playback_buf(0) , _capture_buf(0) - , _source_port(0) - , _last_flush_frame(0) , _note_mode(Sustained) , _frames_written_to_ringbuffer(0) , _frames_read_from_ringbuffer(0) + , _frames_pending_write(0) + , _num_captured_loops(0) + , _accumulated_capture_offset(0) + , _gui_feed_buffer(AudioEngine::instance()->raw_buffer_size (DataType::MIDI)) { in_set_state = true; @@ -87,18 +87,22 @@ MidiDiskstream::MidiDiskstream (Session &sess, const string &name, Diskstream::F in_set_state = false; - assert(!destructive()); + if (destructive()) { + throw failed_constructor(); + } } MidiDiskstream::MidiDiskstream (Session& sess, const XMLNode& node) : Diskstream(sess, node) , _playback_buf(0) , _capture_buf(0) - , _source_port(0) - , _last_flush_frame(0) , _note_mode(Sustained) , _frames_written_to_ringbuffer(0) , _frames_read_from_ringbuffer(0) + , _frames_pending_write(0) + , _num_captured_loops(0) + , _accumulated_capture_offset(0) + , _gui_feed_buffer(AudioEngine::instance()->raw_buffer_size (DataType::MIDI)) { in_set_state = true; @@ -130,13 +134,14 @@ MidiDiskstream::init () _capture_buf = new MidiRingBuffer(size); _n_channels = ChanCount(DataType::MIDI, 1); - - assert(recordable()); + interpolation.add_channel_to (0,0); } MidiDiskstream::~MidiDiskstream () { - Glib::Mutex::Lock lm (state_lock); + Glib::Threads::Mutex::Lock lm (state_lock); + delete _playback_buf; + delete _capture_buf; } @@ -154,7 +159,7 @@ void MidiDiskstream::non_realtime_input_change () { { - Glib::Mutex::Lock lm (state_lock); + Glib::Threads::Mutex::Lock lm (state_lock); if (input_change_pending.type == IOChange::NoChange) { return; @@ -172,7 +177,7 @@ MidiDiskstream::non_realtime_input_change () } if (ni == 0) { - _source_port = 0; + _source_port.reset (); } else { _source_port = _io->midi(0); } @@ -202,7 +207,8 @@ MidiDiskstream::non_realtime_input_change () seek (_session.transport_frame()); } - _last_flush_frame = _session.transport_frame(); + g_atomic_int_set(const_cast (&_frames_pending_write), 0); + g_atomic_int_set(const_cast (&_num_captured_loops), 0); } int @@ -215,7 +221,7 @@ MidiDiskstream::find_and_use_playlist (const string& name) } if (!playlist) { - error << string_compose(_("MidiDiskstream: Playlist \"%1\" isn't an midi playlist"), name) << endmsg; + error << string_compose(_("MidiDiskstream: Playlist \"%1\" isn't a midi playlist"), name) << endmsg; return -1; } @@ -225,9 +231,9 @@ MidiDiskstream::find_and_use_playlist (const string& name) int MidiDiskstream::use_playlist (boost::shared_ptr playlist) { - assert(boost::dynamic_pointer_cast(playlist)); - - Diskstream::use_playlist(playlist); + if (boost::dynamic_pointer_cast(playlist)) { + Diskstream::use_playlist(playlist); + } return 0; } @@ -251,7 +257,6 @@ MidiDiskstream::use_new_playlist () if ((playlist = boost::dynamic_pointer_cast (PlaylistFactory::create ( DataType::MIDI, _session, newname, hidden()))) != 0) { - playlist->set_orig_diskstream_id (id()); return use_playlist (playlist); } else { @@ -262,8 +267,6 @@ MidiDiskstream::use_new_playlist () int MidiDiskstream::use_copy_playlist () { - assert(midi_playlist()); - if (destructive()) { return 0; } @@ -279,7 +282,6 @@ MidiDiskstream::use_copy_playlist () newname = Playlist::bump_name (_playlist->name(), _session); if ((playlist = boost::dynamic_pointer_cast(PlaylistFactory::create (midi_playlist(), newname))) != 0) { - playlist->set_orig_diskstream_id (id()); return use_playlist (playlist); } else { return -1; @@ -291,9 +293,7 @@ MidiDiskstream::use_copy_playlist () int MidiDiskstream::set_destructive (bool yn) { - assert( ! destructive()); - assert( ! yn); - return -1; + return yn ? -1 : 0; } void @@ -305,183 +305,34 @@ MidiDiskstream::set_note_mode (NoteMode m) _write_source->model()->set_note_mode(m); } -#if 0 +/** 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 -trace_midi (ostream& o, MIDI::byte *msg, size_t len) +get_location_times(const Location* location, + framepos_t* start, + framepos_t* end, + framepos_t* length) { - using namespace MIDI; - eventType type; - const char trace_prefix = ':'; - - type = (eventType) (msg[0]&0xF0); - - switch (type) { - case off: - o << trace_prefix - << "Channel " - << (msg[0]&0xF)+1 - << " NoteOff NoteNum " - << (int) msg[1] - << " Vel " - << (int) msg[2] - << endl; - break; - - case on: - o << trace_prefix - << "Channel " - << (msg[0]&0xF)+1 - << " NoteOn NoteNum " - << (int) msg[1] - << " Vel " - << (int) msg[2] - << endl; - break; - - case polypress: - o << trace_prefix - << "Channel " - << (msg[0]&0xF)+1 - << " PolyPressure" - << (int) msg[1] - << endl; - break; - - case MIDI::controller: - o << trace_prefix - << "Channel " - << (msg[0]&0xF)+1 - << " Controller " - << (int) msg[1] - << " Value " - << (int) msg[2] - << endl; - break; - - case program: - o << trace_prefix - << "Channel " - << (msg[0]&0xF)+1 - << " Program Change ProgNum " - << (int) msg[1] - << endl; - break; - - case chanpress: - o << trace_prefix - << "Channel " - << (msg[0]&0xF)+1 - << " Channel Pressure " - << (int) msg[1] - << endl; - break; - - case MIDI::pitchbend: - o << trace_prefix - << "Channel " - << (msg[0]&0xF)+1 - << " Pitch Bend " - << ((msg[2]<<7)|msg[1]) - << endl; - break; - - case MIDI::sysex: - if (len == 1) { - switch (msg[0]) { - case 0xf8: - o << trace_prefix - << "Clock" - << endl; - break; - case 0xfa: - o << trace_prefix - << "Start" - << endl; - break; - case 0xfb: - o << trace_prefix - << "Continue" - << endl; - break; - case 0xfc: - o << trace_prefix - << "Stop" - << endl; - break; - case 0xfe: - o << trace_prefix - << "Active Sense" - << endl; - break; - case 0xff: - o << trace_prefix - << "System Reset" - << endl; - break; - default: - o << trace_prefix - << "System Exclusive (1 byte : " << hex << (int) *msg << dec << ')' - << endl; - break; - } - } else { - o << trace_prefix - << "System Exclusive (" << len << ") = [ " << hex; - for (unsigned int i = 0; i < len; ++i) { - o << (int) msg[i] << ' '; - } - o << dec << ']' << endl; - - } - break; - - case MIDI::song: - o << trace_prefix << "Song" << endl; - break; - - case MIDI::tune: - o << trace_prefix << "Tune" << endl; - break; - - case MIDI::eox: - o << trace_prefix << "End-of-System Exclusive" << endl; - break; - - case MIDI::timing: - o << trace_prefix << "Timing" << endl; - break; - - case MIDI::start: - o << trace_prefix << "Start" << endl; - break; - - case MIDI::stop: - o << trace_prefix << "Stop" << endl; - break; - - case MIDI::contineu: - o << trace_prefix << "Continue" << endl; - break; - - case active: - o << trace_prefix << "Active Sense" << endl; - break; - - default: - o << trace_prefix << "Unrecognized MIDI message" << endl; - break; + if (location) { + *start = location->start(); + *end = location->end(); + *length = *end - *start; } } -#endif int -MidiDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool can_record, bool rec_monitors_input, bool& need_butler) +MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t nframes, framecnt_t& playback_distance, bool need_disk_signal) { - int ret = -1; framecnt_t rec_offset = 0; framecnt_t rec_nframes = 0; bool nominally_recording; bool re = record_enabled (); + bool can_record = _session.actively_recording (); playback_distance = 0; @@ -493,26 +344,55 @@ MidiDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool can return 0; } - if (_source_port == 0) { + boost::shared_ptr sp = _source_port.lock (); + + if (sp == 0) { return 1; } - Glib::Mutex::Lock sm (state_lock, Glib::TRY_LOCK); + Glib::Threads::Mutex::Lock sm (state_lock, Glib::Threads::TRY_LOCK); if (!sm.locked()) { 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())) { - OverlapType ot = coverage (first_recordable_frame, last_recordable_frame, transport_frame, transport_frame + nframes); + Evoral::OverlapType ot = Evoral::coverage (first_recordable_frame, last_recordable_frame, transport_frame, transport_frame + nframes); + // XXX should this be transport_frame + nframes - 1 ? coverage() expects its parameter ranges to include their end points calculate_record_range(ot, transport_frame, nframes, rec_nframes, rec_offset); + /* For audio: not writing frames to the capture ringbuffer offsets + * the recording. For midi: we need to keep track of the record range + * and subtract the accumulated difference from the event time. + */ + if (rec_nframes) { + _accumulated_capture_offset += rec_offset; + } else { + _accumulated_capture_offset += nframes; + } if (rec_nframes && !was_recording) { - _write_source->mark_write_starting_now (); - capture_captured = 0; + 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(const_cast (&_frames_pending_write), 0); + g_atomic_int_set(const_cast (&_num_captured_loops), 0); was_recording = true; } } @@ -522,14 +402,18 @@ MidiDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool can } if (nominally_recording || rec_nframes) { + // Pump entire port buffer into the ring buffer (TODO: split cycles?) + MidiBuffer& buf = sp->get_midi_buffer(nframes); + MidiTrack* mt = dynamic_cast(_track); + MidiChannelFilter* filter = mt ? &mt->capture_filter() : NULL; - // Pump entire port buffer into the ring buffer (FIXME: split cycles?) - MidiBuffer& buf = _source_port->get_midi_buffer(nframes); for (MidiBuffer::iterator i = buf.begin(); i != buf.end(); ++i) { - const Evoral::MIDIEvent ev(*i, false); - assert(ev.buffer()); + Evoral::MIDIEvent ev(*i, false); + if (ev.time() + rec_offset > rec_nframes) { + break; + } #ifndef NDEBUG - if (DEBUG::MidiIO & PBD::debug_bits) { + if (DEBUG_ENABLED(DEBUG::MidiIO)) { const uint8_t* __data = ev.buffer(); DEBUG_STR_DECL(a); DEBUG_STR_APPEND(a, string_compose ("mididiskstream %1 capture event @ %2 + %3 sz %4 ", this, ev.time(), transport_frame, ev.size())); @@ -543,28 +427,54 @@ MidiDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool can 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 + probably be implemented in the source instead of here. + */ + const framecnt_t loop_offset = _num_captured_loops * loop_length; + const framepos_t event_time = transport_frame + loop_offset - _accumulated_capture_offset + ev.time(); + if (event_time < 0 || event_time < first_recordable_frame) { + /* Event out of range, skip */ + continue; + } + + if (!filter || !filter->filter(ev.buffer(), ev.size())) { + _capture_buf->write(event_time, ev.type(), ev.size(), ev.buffer()); + } } + g_atomic_int_add(const_cast(&_frames_pending_write), nframes); if (buf.size() != 0) { - /* XXX this needs fixing - realtime new() call for - every time we get MIDI data in a process callback! - */ - - /* Make a copy of this data and emit it for the GUI to see */ - boost::shared_ptr copy (new MidiBuffer (buf.capacity ())); - for (MidiBuffer::iterator i = buf.begin(); i != buf.end(); ++i) { - copy->push_back ((*i).time() + transport_frame, (*i).size(), (*i).buffer()); + Glib::Threads::Mutex::Lock lm (_gui_feed_buffer_mutex, Glib::Threads::TRY_LOCK); + + if (lm.locked ()) { + /* Copy this data into our GUI feed buffer and tell the GUI + 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. + */ + _gui_feed_buffer.push_back ((*i).time() + transport_frame, (*i).size(), (*i).buffer()); + } } - DataRecorded (copy, _write_source); /* EMIT SIGNAL */ + DataRecorded (_write_source); /* EMIT SIGNAL */ } } else { if (was_recording) { - finish_capture (rec_monitors_input); + finish_capture (); } + _accumulated_capture_offset = 0; } @@ -586,28 +496,66 @@ MidiDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool can playback_distance = nframes; - } else { + } else if (_actual_speed != 1.0f && _target_speed > 0) { + + interpolation.set_speed (_target_speed); - /* XXX: should be doing varispeed stuff here, similar to the code in AudioDiskstream::process */ + playback_distance = interpolation.distance (nframes); + } else { playback_distance = nframes; + } + + if (need_disk_signal) { + /* copy the diskstream data to all output buffers */ + MidiBuffer& mbuf (bufs.get_midi (0)); + get_playback (mbuf, playback_distance); + + /* leave the audio count alone */ + ChanCount cnt (DataType::MIDI, 1); + cnt.set (DataType::AUDIO, bufs.count().n_audio()); + bufs.set_count (cnt); + + /* vari-speed */ + if (_target_speed > 0 && _actual_speed != 1.0f) { + MidiBuffer& mbuf (bufs.get_midi (0)); + for (MidiBuffer::iterator i = mbuf.begin(); i != mbuf.end(); ++i) { + MidiBuffer::TimeType *tme = i.timeptr(); + *tme = (*tme) * nframes / playback_distance; + } + } } - ret = 0; + return 0; +} - if (commit (nframes)) { - need_butler = true; +frameoffset_t +MidiDiskstream::calculate_playback_distance (pframes_t nframes) +{ + frameoffset_t playback_distance = nframes; + + if (!record_enabled() && _actual_speed != 1.0f && _actual_speed > 0.f) { + interpolation.set_speed (_target_speed); + playback_distance = interpolation.distance (nframes, false); } - return ret; + if (_actual_speed < 0.0) { + return -playback_distance; + } else { + return playback_distance; + } } bool -MidiDiskstream::commit (framecnt_t nframes) +MidiDiskstream::commit (framecnt_t playback_distance) { bool need_butler = false; + if (!_io || !_io->active()) { + return false; + } + if (_actual_speed < 0.0) { playback_sample -= playback_distance; } else { @@ -619,15 +567,51 @@ MidiDiskstream::commit (framecnt_t nframes) adjust_capture_position = 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_written - frames_read) + nframes < midi_readahead) { + uint32_t frames_read = g_atomic_int_get(const_cast(&_frames_read_from_ringbuffer)); + uint32_t frames_written = g_atomic_int_get(const_cast(&_frames_written_to_ringbuffer)); + + /* + cerr << name() << " MDS written: " << frames_written << " - read: " << frames_read << + " = " << frames_written - frames_read + << " + " << playback_distance << " < " << midi_readahead << " = " << need_butler << ")" << endl; + */ + + /* frames_read will generally be less than frames_written, but + * immediately after an overwrite, we can end up having read some data + * before we've written any. we don't need to trip an assert() on this, + * 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. + * The RT-thread meanwhile will happily continue and + * ‘frames_read’ (from buffer to output) will become larger + * than ‘frames_written’ (from disk to buffer). + * + * The disk-stream is now behind.. + * + * In those cases the butler needs to be summed to refill the buffer (done now) + * AND we need to skip (frames_read - frames_written). ie remove old events + * before playback_sample from the rinbuffer. + * + * [1] one way to do so is described at #6170. + * For me just popping up the context-menu on a MIDI-track header + * of a track with a large (think beethoven :) midi-region also did the + * trick. The playhead stalls for 2 or 3 sec, until the context-menu shows. + * + * In both cases the root cause is that redrawing MIDI regions on the GUI is still very slow + * and can stall + */ + if (frames_read <= frames_written) { + if ((frames_written - frames_read) + playback_distance < midi_readahead) { + need_butler = true; + } + } else { need_butler = true; } - /*cerr << "MDS written: " << frames_written << " - read: " << frames_read << - " = " << frames_written - frames_read - << " + " << nframes << " < " << midi_readahead << " = " << need_butler << ")" << endl;*/ return need_butler; } @@ -644,13 +628,21 @@ MidiDiskstream::set_pending_overwrite (bool yn) int MidiDiskstream::overwrite_existing_buffers () { - /* This is safe as long as the butler thread is suspended, which it should be */ + /* Clear the playback buffer contents. This is safe as long as the butler + thread is suspended, which it should be. */ _playback_buf->reset (); + _playback_buf->reset_tracker (); g_atomic_int_set (&_frames_read_from_ringbuffer, 0); g_atomic_int_set (&_frames_written_to_ringbuffer, 0); - read (overwrite_frame, disk_io_chunk_frames, false); + /* Resolve all currently active notes in the playlist. This is more + aggressive than it needs to be: ideally we would only resolve what is + absolutely necessary, but this seems difficult and/or impossible without + having the old data or knowing what change caused the overwrite. */ + midi_playlist()->resolve_note_trackers (*_playback_buf, overwrite_frame); + + read (overwrite_frame, disk_read_chunk_frames, false); file_frame = overwrite_frame; // it was adjusted by ::read() overwrite_queued = false; _pending_overwrite = false; @@ -661,9 +653,17 @@ MidiDiskstream::overwrite_existing_buffers () int MidiDiskstream::seek (framepos_t frame, bool complete_refill) { - Glib::Mutex::Lock lm (state_lock); + Glib::Threads::Mutex::Lock lm (state_lock); int ret = -1; + if (g_atomic_int_get (&_frames_read_from_ringbuffer) == 0) { + /* we haven't read anything since the last seek, + so flush all note trackers to prevent + wierdness + */ + reset_tracker (); + } + _playback_buf->reset(); _capture_buf->reset(); g_atomic_int_set(&_frames_read_from_ringbuffer, 0); @@ -702,29 +702,20 @@ 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; + MidiTrack* mt = dynamic_cast(_track); + MidiChannelFilter* filter = mt ? &mt->playback_filter() : NULL; - /* 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 (!reversed) { - 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. @@ -735,16 +726,16 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed) start = loop_start + ((start - loop_start) % loop_length); //cerr << "to " << start << endl; } - //cerr << "start is " << start << " loopstart: " << loop_start << " loopend: " << loop_end << endl; + // cerr << "start is " << start << " end " << start+dur << " loopstart: " << loop_start << " loopend: " << loop_end << endl; } while (dur) { /* take any loop into account. we can't read past the end of the loop. */ - if (loc && (loop_end - start < dur)) { + if (loc && (loop_end - start <= dur)) { this_read = loop_end - start; - //cerr << "reloop true: thisread: " << this_read << " dur: " << dur << endl; + // cerr << "reloop true: thisread: " << this_read << " dur: " << dur << endl; reloop = true; } else { reloop = false; @@ -757,16 +748,14 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed) this_read = min(dur,this_read); - if (midi_playlist()->read (*_playback_buf, start, this_read) != this_read) { + if (midi_playlist()->read (*_playback_buf, start, this_read, 0, filter) != this_read) { error << string_compose( _("MidiDiskstream %1: cannot read %2 from playlist at frame %3"), - _id, this_read, start) << endmsg; + id(), this_read, start) << endmsg; return -1; } - g_atomic_int_add(&_frames_written_to_ringbuffer, this_read); - - _read_data_count = _playlist->read_data_count(); + g_atomic_int_add (&_frames_written_to_ringbuffer, this_read); if (reversed) { @@ -777,11 +766,9 @@ 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. - _playback_buf->write(loop_end - 1, LoopEventType, sizeof (framepos_t), (uint8_t *) &loop_start); start = loop_start; } else { start += this_read; @@ -796,7 +783,7 @@ MidiDiskstream::read (framepos_t& start, framecnt_t dur, bool reversed) } int -MidiDiskstream::do_refill_with_alloc () +MidiDiskstream::_do_refill_with_alloc (bool /* partial_fill */) { return do_refill(); } @@ -821,24 +808,24 @@ MidiDiskstream::do_refill () return 0; } - // At this point we... - assert(_playback_buf->write_space() > 0); // ... have something to write to, and - assert(file_frame <= max_framepos); // ... something to write + /* no space to write */ + if (_playback_buf->write_space() == 0) { + return 0; + } - // now calculate how much time is in the ringbuffer. - // and lets write as much as we need to get this to be midi_readahead; 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_written - frames_read) >= midi_readahead) { + if ((frames_read < frames_written) && (frames_written - frames_read) >= midi_readahead) { return 0; } - framecnt_t to_read = midi_readahead - (frames_written - 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 = (framecnt_t) min ((framecnt_t) 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); if (read (file_frame, to_read, reversed)) { ret = -1; @@ -849,36 +836,34 @@ MidiDiskstream::do_refill () /** Flush pending data to disk. * - * Important note: this function will write *AT MOST* disk_io_chunk_frames + * Important note: this function will write *AT MOST* disk_write_chunk_frames * of data to disk. it will never write more than that. If it writes that * much and there is more than that waiting to be written, it will return 1, * otherwise 0 on success or -1 on failure. * - * If there is less than disk_io_chunk_frames to be written, no data will be + * If there is less than disk_write_chunk_frames to be written, no data will be * written at all unless @a force_flush is true. */ int MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush) { - uint32_t to_write; + framecnt_t to_write; int32_t ret = 0; - framecnt_t total; - - _write_data_count = 0; - total = _session.transport_frame() - _last_flush_frame; - - if (_last_flush_frame > _session.transport_frame() || _last_flush_frame < capture_start_frame) { - _last_flush_frame = _session.transport_frame(); + if (!_write_source) { + return 0; } - if (total == 0 || _capture_buf->read_space() == 0 - || (!force_flush && (total < disk_io_chunk_frames && was_recording))) { + const framecnt_t total = g_atomic_int_get(const_cast (&_frames_pending_write)); + + if (total == 0 || + _capture_buf->read_space() == 0 || + (!force_flush && (total < disk_write_chunk_frames) && was_recording)) { goto out; } /* if there are 2+ chunks of disk i/o possible for - this track, let the caller know so that it can arrange + this track), let the caller know so that it can arrange for us to be called again, ASAP. if we are forcing a flush, then if there is* any* extra @@ -888,23 +873,24 @@ MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush) let the caller know too. */ - if (total >= 2 * disk_io_chunk_frames || ((force_flush || !was_recording) && total > disk_io_chunk_frames)) { + if (total >= 2 * disk_write_chunk_frames || ((force_flush || !was_recording) && total > disk_write_chunk_frames)) { ret = 1; } - to_write = disk_io_chunk_frames; - - assert(!destructive()); + if (force_flush) { + /* push out everything we have, right now */ + to_write = max_framecnt; + } else { + to_write = disk_write_chunk_frames; + } - if (record_enabled() && - ((_session.transport_frame() - _last_flush_frame > disk_io_chunk_frames) || - force_flush)) { - if ((!_write_source) || _write_source->midi_write (*_capture_buf, get_capture_start_frame (0), to_write) != to_write) { - error << string_compose(_("MidiDiskstream %1: cannot write to disk"), _id) << endmsg; + if (record_enabled() && ((total > disk_write_chunk_frames) || force_flush)) { + Source::Lock lm(_write_source->mutex()); + 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; - } else { - _last_flush_frame = _session.transport_frame(); } + g_atomic_int_add(const_cast (&_frames_pending_write), -to_write); } out: @@ -921,7 +907,7 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen MidiRegion::SourceList::iterator src; vector::iterator ci; - finish_capture (true); + finish_capture (); /* butler is already stopped, but there may be work to do to flush remaining data to disk. @@ -941,10 +927,10 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen } /* XXX is there anything we can do if err != 0 ? */ - Glib::Mutex::Lock lm (capture_info_lock); + Glib::Threads::Mutex::Lock lm (capture_info_lock); if (capture_info.empty()) { - return; + goto no_capture_stuff_to_do; } if (abort_capture) { @@ -959,8 +945,6 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen } else { - assert(_write_source); - framecnt_t total_capture = 0; for (ci = capture_info.begin(); ci != capture_info.end(); ++ci) { total_capture += (*ci)->frames; @@ -970,6 +954,8 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen /* phew, we have data */ + Source::Lock source_lock(_write_source->mutex()); + /* figure out the name for this take */ srcs.push_back (_write_source); @@ -977,17 +963,17 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen _write_source->set_timeline_position (capture_info.front()->start); _write_source->set_captured_for (_name); + /* set length in beats to entire capture length */ + + BeatsFramesConverter converter (_session.tempo_map(), capture_info.front()->start); + const Evoral::Beats total_capture_beats = converter.from (total_capture); + _write_source->set_length_beats (total_capture_beats); + /* flush to disk: this step differs from the audio path, where all the data is already on disk. */ - _write_source->mark_streaming_write_completed (); - - /* set length in beats to entire capture length */ - - BeatsFramesConverter converter (_session.tempo_map(), capture_info.front()->start); - const double total_capture_beats = converter.from(total_capture); - _write_source->set_length_beats(total_capture_beats); + _write_source->mark_midi_streaming_write_completed (source_lock, Evoral::Sequence::ResolveStuckNotes, total_capture_beats); /* we will want to be able to keep (over)writing the source but we don't want it to be removable. this also differs @@ -1045,6 +1031,10 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen RegionFactory::region_name (region_name, _write_source->name(), false); + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1 capture start @ %2 length %3 add new region %4\n", + _name, (*ci)->start, (*ci)->frames, region_name)); + + // cerr << _name << ": based on ci of " << (*ci)->start << " for " << (*ci)->frames << " add a region\n"; try { @@ -1097,45 +1087,31 @@ MidiDiskstream::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen delete *ci; } - if (_playlist) { - midi_playlist()->clear_note_trackers (); - } - capture_info.clear (); capture_start_frame = 0; + + no_capture_stuff_to_do: + + reset_tracker (); } void -MidiDiskstream::transport_looped (framepos_t transport_frame) +MidiDiskstream::transport_looped (framepos_t) { + /* 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 (true); - - // 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; + g_atomic_int_add(const_cast (&_num_captured_loops), 1); } } void -MidiDiskstream::finish_capture (bool /*rec_monitors_input*/) +MidiDiskstream::finish_capture () { was_recording = false; @@ -1143,9 +1119,6 @@ MidiDiskstream::finish_capture (bool /*rec_monitors_input*/) return; } - // Why must we destroy? - assert(!destructive()); - CaptureInfo* ci = new CaptureInfo; ci->start = capture_start_frame; @@ -1169,12 +1142,10 @@ MidiDiskstream::finish_capture (bool /*rec_monitors_input*/) void MidiDiskstream::set_record_enabled (bool yn) { - if (!recordable() || !_session.record_enabling_legal()) { + if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_midi() == 0 || record_safe ()) { return; } - assert(!destructive()); - /* yes, i know that this not proof against race conditions, but its good enough. i think. */ @@ -1185,28 +1156,56 @@ MidiDiskstream::set_record_enabled (bool yn) } else { disengage_record_enable (); } + + RecordEnableChanged (); /* EMIT SIGNAL */ } } void -MidiDiskstream::engage_record_enable () +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 */ + } +} + +bool +MidiDiskstream::prep_record_enable () { + if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_midi() == 0 || record_safe ()) { // REQUIRES REVIEW "|| record_safe ()" + return false; + } + bool const rolling = _session.transport_speed() != 0.0f; - g_atomic_int_set (&_record_enabled, 1); + boost::shared_ptr sp = _source_port.lock (); - if (_source_port && Config->get_monitoring_model() == HardwareMonitoring) { - _source_port->request_monitor_input (!(_session.config.get_auto_input() && rolling)); + if (sp && Config->get_monitoring_model() == HardwareMonitoring) { + sp->request_input_monitoring (!(_session.config.get_auto_input() && rolling)); } - RecordEnableChanged (); /* EMIT SIGNAL */ + return true; } -void -MidiDiskstream::disengage_record_enable () +bool +MidiDiskstream::prep_record_disable () { - g_atomic_int_set (&_record_enabled, 0); - RecordEnableChanged (); /* EMIT SIGNAL */ + + return true; } XMLNode& @@ -1214,11 +1213,7 @@ MidiDiskstream::get_state () { XMLNode& node (Diskstream::get_state()); char buf[64]; - LocaleGuard lg (X_("POSIX")); - - node.add_property("channel-mode", enum_2_string(get_channel_mode())); - snprintf (buf, sizeof(buf), "0x%x", get_channel_mask()); - node.add_property("channel-mask", buf); + LocaleGuard lg; if (_write_source && _session.get_record_enabled()) { @@ -1249,19 +1244,16 @@ MidiDiskstream::get_state () int MidiDiskstream::set_state (const XMLNode& node, int version) { - const XMLProperty* prop; XMLNodeList nlist = node.children(); XMLNodeIterator niter; XMLNode* capture_pending_node = 0; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg; /* prevent write sources from being created */ in_set_state = true; for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - assert ((*niter)->name() != IO::state_node_name); - if ((*niter)->name() == X_("CapturingSources")) { capture_pending_node = *niter; } @@ -1271,26 +1263,10 @@ MidiDiskstream::set_state (const XMLNode& node, int version) return -1; } - ChannelMode channel_mode = AllChannels; - if ((prop = node.property ("channel-mode")) != 0) { - channel_mode = ChannelMode (string_2_enum(prop->value(), channel_mode)); - } - - unsigned int channel_mask = 0xFFFF; - if ((prop = node.property ("channel-mask")) != 0) { - sscanf (prop->value().c_str(), "0x%x", &channel_mask); - if (channel_mask & (~0xFFFF)) { - warning << _("MidiDiskstream: XML property channel-mask out of range") << endmsg; - } - } - - if (capture_pending_node) { use_pending_capture_data (*capture_pending_node); } - set_channel_mode (channel_mode, channel_mask); - in_set_state = false; return 0; @@ -1303,13 +1279,12 @@ MidiDiskstream::use_new_write_source (uint32_t n) return 1; } - assert(n == 0); - + _accumulated_capture_offset = 0; _write_source.reset(); try { _write_source = boost::dynamic_pointer_cast( - _session.create_midi_source_for_session (0, name ())); + _session.create_midi_source_for_session (write_source_name ())); if (!_write_source) { throw failed_constructor(); @@ -1324,25 +1299,36 @@ MidiDiskstream::use_new_write_source (uint32_t n) return 0; } - -list > -MidiDiskstream::steal_write_sources() +/** + * We want to use the name of the existing write source (the one that will be + * used by the next capture) for another purpose. So change the name of the + * current source, and return its current name. + * + * Return an empty string if the change cannot be accomplished. + */ +std::string +MidiDiskstream::steal_write_source_name () { - list > ret; - - /* put some data on the disk, even if its just a header for an empty file */ - boost::dynamic_pointer_cast (_write_source)->ensure_disk_file (); + string our_old_name = _write_source->name(); - /* never let it go away */ - _write_source->mark_nonremovable (); + /* this will bump the name of the current write source to the next one + * (e.g. "MIDI 1-1" gets renamed to "MIDI 1-2"), thus leaving the + * current write source name (e.g. "MIDI 1-1" available). See the + * comments in Session::create_midi_source_by_stealing_name() about why + * we do this. + */ - ret.push_back (_write_source); - - /* get a new one */ + try { + string new_path = _session.new_midi_source_path (name()); - use_new_write_source (0); + if (_write_source->rename (new_path)) { + return string(); + } + } catch (...) { + return string (); + } - return ret; + return our_old_name; } void @@ -1353,7 +1339,8 @@ MidiDiskstream::reset_write_sources (bool mark_write_complete, bool /*force*/) } if (_write_source && mark_write_complete) { - _write_source->mark_streaming_write_completed (); + Source::Lock lm(_write_source->mutex()); + _write_source->mark_streaming_write_completed (lm); } use_new_write_source (0); } @@ -1369,10 +1356,13 @@ MidiDiskstream::allocate_temporary_buffers () } void -MidiDiskstream::monitor_input (bool yn) +MidiDiskstream::ensure_input_monitoring (bool yn) { - if (_source_port) - _source_port->ensure_monitor_input (yn); + boost::shared_ptr sp = _source_port.lock (); + + if (sp) { + sp->ensure_input_monitoring (yn); + } } void @@ -1393,15 +1383,25 @@ MidiDiskstream::set_align_style_from_io () float MidiDiskstream::playback_buffer_load () const { - return (float) ((double) _playback_buf->read_space()/ - (double) _playback_buf->capacity()); + /* 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. + + If we use a simple buffer load computation, we will report that the MIDI diskstream + 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; } float MidiDiskstream::capture_buffer_load () const { - return (float) ((double) _capture_buf->write_space()/ - (double) _capture_buf->capacity()); + /* We don't report playback buffer load, so don't report capture load either */ + + return 1; } int @@ -1410,48 +1410,108 @@ MidiDiskstream::use_pending_capture_data (XMLNode& /*node*/) return 0; } -/** Writes playback events in the given range to \a dst, translating time stamps - * so that an event at \a start has time = 0 +void +MidiDiskstream::flush_playback (framepos_t start, framepos_t end) +{ + _playback_buf->flush (start, end); + g_atomic_int_add (&_frames_read_from_ringbuffer, end - start); +} + +/** Writes playback events from playback_sample for nframes to dst, translating time stamps + * so that an event at playback_sample has time = 0 */ void -MidiDiskstream::get_playback (MidiBuffer& dst, framepos_t start, framepos_t end) +MidiDiskstream::get_playback (MidiBuffer& dst, framecnt_t nframes) { dst.clear(); - assert(dst.size() == 0); - // Reverse. ... We just don't do reverse, ok? Back off. - if (end <= start) { - return; - } + Location* loc = loop_location; - // Translate stamps to be relative to start + 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)); + // cerr << "================\n"; + // _playback_buf->dump (cerr); + // cerr << "----------------\n"; -#ifndef NDEBUG - DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ( - "%1 MDS pre-read read %4..%5 from %2 write to %3\n", _name, - _playback_buf->get_read_ptr(), _playback_buf->get_write_ptr(), start, end)); -// cerr << "================\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; + } + + DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("looped, effective start adjusted to %1\n", effective_start)); + + if (effective_start == loc->start()) { + /* We need to turn off notes that may extend + beyond the loop end. + */ + + _playback_buf->resolve_tracker (dst, split_cycle_offset); + } + + _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)); + + 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, split_cycle_offset); + } + + 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, split_cycle_offset); + } + + } else { + DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("loop read #3, adjusted start as %1 for %2\n", + effective_start, nframes)); + events_read = _playback_buf->read (dst, effective_start, effective_start + nframes, split_cycle_offset); + } + } else { + _playback_buf->skip_to (playback_sample); + events_read = _playback_buf->read (dst, playback_sample, playback_sample + nframes, split_cycle_offset); + } - const size_t events_read = _playback_buf->read(dst, start, end); DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ( "%1 MDS events read %2 range %3 .. %4 rspace %5 wspace %6 r@%7 w@%8\n", - _name, events_read, start, end, + _name, events_read, playback_sample, playback_sample + nframes, _playback_buf->read_space(), _playback_buf->write_space(), - _playback_buf->get_read_ptr(), _playback_buf->get_write_ptr())); -#else - _playback_buf->read(dst, start, end); -#endif + _playback_buf->get_read_ptr(), _playback_buf->get_write_ptr())); - gint32 frames_read = end - start; - g_atomic_int_add(&_frames_read_from_ringbuffer, frames_read); + g_atomic_int_add (&_frames_read_from_ringbuffer, nframes); } bool MidiDiskstream::set_name (string const & name) { + if (_name == name) { + return true; + } Diskstream::set_name (name); /* get a new write source so that its name reflects the new diskstream name */ @@ -1460,3 +1520,56 @@ MidiDiskstream::set_name (string const & name) return true; } +bool +MidiDiskstream::set_write_source_name (const std::string& str) { + if (_write_source_name == str) { + return true; + } + Diskstream::set_write_source_name (str); + if (_write_source_name == name()) { + return true; + } + use_new_write_source (0); + return true; +} + +boost::shared_ptr +MidiDiskstream::get_gui_feed_buffer () const +{ + boost::shared_ptr 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; +} + +void +MidiDiskstream::reset_tracker () +{ + _playback_buf->reset_tracker (); + + boost::shared_ptr mp (midi_playlist()); + + if (mp) { + mp->reset_note_trackers (); + } +} + +void +MidiDiskstream::resolve_tracker (Evoral::EventSink& buffer, framepos_t time) +{ + _playback_buf->resolve_tracker(buffer, time); + + boost::shared_ptr mp (midi_playlist()); + + if (mp) { + mp->reset_note_trackers (); + } +} + + +boost::shared_ptr +MidiDiskstream::midi_playlist () +{ + return boost::dynamic_pointer_cast(_playlist); +}