diff options
Diffstat (limited to 'libs/ardour/disk_writer.cc')
-rw-r--r-- | libs/ardour/disk_writer.cc | 1000 |
1 files changed, 980 insertions, 20 deletions
diff --git a/libs/ardour/disk_writer.cc b/libs/ardour/disk_writer.cc index c6f5c92c76..32d22fb1ae 100644 --- a/libs/ardour/disk_writer.cc +++ b/libs/ardour/disk_writer.cc @@ -19,12 +19,19 @@ #include "pbd/i18n.h" +#include "ardour/analyser.h" #include "ardour/audioengine.h" -#include "ardour/audio_buffer.h" #include "ardour/audiofilesource.h" +#include "ardour/audio_buffer.h" +#include "ardour/audioplaylist.h" +#include "ardour/audioregion.h" #include "ardour/debug.h" #include "ardour/disk_writer.h" +#include "ardour/midi_playlist.h" +#include "ardour/midi_source.h" +#include "ardour/midi_track.h" #include "ardour/port.h" +#include "ardour/region_factory.h" #include "ardour/session.h" #include "ardour/smf_source.h" @@ -47,6 +54,9 @@ DiskWriter::DiskWriter (Session& s, string const & str, DiskIOProcessor::Flag f) , last_possibly_recording (0) , _alignment_style (ExistingMaterial) , _alignment_choice (Automatic) + , _num_captured_loops (0) + , _accumulated_capture_offset (0) + , _gui_feed_buffer(AudioEngine::instance()->raw_buffer_size (DataType::MIDI)) { DiskIOProcessor::init (); } @@ -475,6 +485,8 @@ DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, ChannelList::iterator chan; framecnt_t rec_offset = 0; framecnt_t rec_nframes = 0; + bool nominally_recording; + bool re = record_enabled (); bool can_record = _session.actively_recording (); check_record_status (start_frame, can_record); @@ -483,6 +495,8 @@ DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, return; } + nominally_recording = (can_record && re); + Glib::Threads::Mutex::Lock sm (state_lock, Glib::Threads::TRY_LOCK); if (!sm.locked()) { @@ -496,7 +510,18 @@ DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, last_recordable_frame = max_framepos; } - if (record_enabled()) { + const Location* const loop_loc = loop_location; + framepos_t loop_start = 0; + framepos_t loop_end = 0; + framepos_t loop_length = 0; + + if (loop_loc) { + 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() || _session.preroll_record_punch_enabled()))) { Evoral::OverlapType ot = Evoral::coverage (first_recordable_frame, last_recordable_frame, start_frame, end_frame); // XXX should this be transport_frame + nframes - 1 ? coverage() expects its parameter ranges to include their end points @@ -507,8 +532,36 @@ DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, if (rec_nframes && !was_recording) { 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 = start_frame - loop_start; + capture_start_frame = loop_start; + } + + _midi_write_source->mark_write_starting_now (capture_start_frame, capture_captured, loop_length); + + g_atomic_int_set(const_cast<gint*> (&_frames_pending_write), 0); + g_atomic_int_set(const_cast<gint*> (&_num_captured_loops), 0); + was_recording = true; + } + + /* 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 (can_record && !_last_capture_sources.empty()) { @@ -517,6 +570,8 @@ DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, if (rec_nframes) { + /* AUDIO */ + const size_t n_buffers = bufs.count().n_audio(); for (n = 0; chan != c->end(); ++chan, ++n) { @@ -548,38 +603,106 @@ DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, memcpy (chaninfo->rw_vector.buf[0], incoming, sizeof (Sample) * first); memcpy (chaninfo->rw_vector.buf[1], incoming + first, sizeof (Sample) * (rec_nframes - first)); } - } - } else { + chaninfo->buf->increment_write_ptr (rec_nframes); - if (was_recording) { - finish_capture (c); } - } + /* MIDI */ - for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - if (rec_nframes) { - (*chan)->buf->increment_write_ptr (rec_nframes); + // Pump entire port buffer into the ring buffer (TODO: split cycles?) + MidiBuffer& buf = bufs.get_midi (0); + boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack>(_route); + MidiChannelFilter* filter = mt ? &mt->capture_filter() : 0; + + for (MidiBuffer::iterator i = buf.begin(); i != buf.end(); ++i) { + Evoral::Event<MidiBuffer::TimeType> ev(*i, false); + if (ev.time() + rec_offset > rec_nframes) { + break; + } +#ifndef NDEBUG + 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(), start_frame, ev.size())); + for (size_t i=0; i < ev.size(); ++i) { + DEBUG_STR_APPEND(a,hex); + DEBUG_STR_APPEND(a,"0x"); + DEBUG_STR_APPEND(a,(int)__data[i]); + DEBUG_STR_APPEND(a,' '); + } + DEBUG_STR_APPEND(a,'\n'); + DEBUG_TRACE (DEBUG::MidiIO, DEBUG_STR(a).str()); + } +#endif + /* 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 = start_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())) { + _midi_buf->write (event_time, ev.event_type(), ev.size(), ev.buffer()); + } + } + g_atomic_int_add(const_cast<gint*>(&_frames_pending_write), nframes); + + if (buf.size() != 0) { + 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() + start_frame, (*i).size(), (*i).buffer()); + } + } + + DataRecorded (_midi_write_source); /* EMIT SIGNAL */ } - } - if (rec_nframes != 0) { capture_captured += rec_nframes; DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1 now captured %2 (by %3)\n", name(), capture_captured, rec_nframes)); + + } else { + + /* not recording this time, but perhaps we were before .. */ + + if (was_recording) { + finish_capture (c); + _accumulated_capture_offset = 0; + } } + /* AUDIO BUTLER REQUIRED CODE */ + if (!c->empty()) { - if (_slaved) { - if (c->front()->buf->write_space() >= c->front()->buf->bufsize() / 2) { - _need_butler = true; - } - } else { - if (((framecnt_t) c->front()->buf->read_space() >= _chunk_frames)) { - _need_butler = true; - } + if (((framecnt_t) c->front()->buf->read_space() >= _chunk_frames)) { + _need_butler = true; } } + + /* MIDI BUTLER REQUIRED CODE */ + + if (_midi_buf->read_space() < _midi_buf->bufsize() / 2) { + _need_butler = true; + } } void @@ -737,3 +860,840 @@ DiskWriter::buffer_load () const return (float) ((double) c->front()->buf->write_space()/ (double) c->front()->buf->bufsize()); } + +void +DiskWriter::set_note_mode (NoteMode m) +{ + _note_mode = m; + + boost::shared_ptr<MidiPlaylist> mp = boost::dynamic_pointer_cast<MidiPlaylist> (_playlists[DataType::MIDI]); + + if (mp) { + mp->set_note_mode (m); + } + + if (_midi_write_source && _midi_write_source->model()) + _midi_write_source->model()->set_note_mode(m); +} + +int +DiskWriter::seek (framepos_t frame, bool complete_refill) +{ + uint32_t n; + ChannelList::iterator chan; + boost::shared_ptr<ChannelList> c = channels.reader(); + + Glib::Threads::Mutex::Lock lm (state_lock); + + for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) { + (*chan)->buf->reset (); + } + + _midi_buf->reset (); + g_atomic_int_set(&_frames_read_from_ringbuffer, 0); + g_atomic_int_set(&_frames_written_to_ringbuffer, 0); + + /* can't rec-enable in destructive mode if transport is before start */ + + if (destructive() && record_enabled() && frame < _session.current_start_frame()) { + disengage_record_enable (); + } + + playback_sample = frame; + file_frame = frame; + + return 0; +} + +int +DiskWriter::do_flush (RunContext ctxt, bool force_flush) +{ + uint32_t to_write; + int32_t ret = 0; + RingBufferNPT<Sample>::rw_vector vector; + RingBufferNPT<CaptureTransition>::rw_vector transvec; + framecnt_t total; + + transvec.buf[0] = 0; + transvec.buf[1] = 0; + vector.buf[0] = 0; + vector.buf[1] = 0; + + boost::shared_ptr<ChannelList> c = channels.reader(); + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + + (*chan)->buf->get_read_vector (&vector); + + total = vector.len[0] + vector.len[1]; + + if (total == 0 || (total < _chunk_frames && !force_flush && 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 + for us to be called again, ASAP. + + if we are forcing a flush, then if there is* any* extra + work, let the caller know. + + if we are no longer recording and there is any extra work, + let the caller know too. + */ + + if (total >= 2 * _chunk_frames || ((force_flush || !was_recording) && total > _chunk_frames)) { + ret = 1; + } + + to_write = min (_chunk_frames, (framecnt_t) vector.len[0]); + + // check the transition buffer when recording destructive + // important that we get this after the capture buf + + if (destructive()) { + (*chan)->capture_transition_buf->get_read_vector(&transvec); + size_t transcount = transvec.len[0] + transvec.len[1]; + size_t ti; + + for (ti=0; ti < transcount; ++ti) { + CaptureTransition & captrans = (ti < transvec.len[0]) ? transvec.buf[0][ti] : transvec.buf[1][ti-transvec.len[0]]; + + if (captrans.type == CaptureStart) { + // by definition, the first data we got above represents the given capture pos + + (*chan)->write_source->mark_capture_start (captrans.capture_val); + (*chan)->curr_capture_cnt = 0; + + } else if (captrans.type == CaptureEnd) { + + // capture end, the capture_val represents total frames in capture + + if (captrans.capture_val <= (*chan)->curr_capture_cnt + to_write) { + + // shorten to make the write a perfect fit + uint32_t nto_write = (captrans.capture_val - (*chan)->curr_capture_cnt); + + if (nto_write < to_write) { + ret = 1; // should we? + } + to_write = nto_write; + + (*chan)->write_source->mark_capture_end (); + + // increment past this transition, but go no further + ++ti; + break; + } + else { + // actually ends just beyond this chunk, so force more work + ret = 1; + break; + } + } + } + + if (ti > 0) { + (*chan)->capture_transition_buf->increment_read_ptr(ti); + } + } + + if ((!(*chan)->write_source) || (*chan)->write_source->write (vector.buf[0], to_write) != to_write) { + error << string_compose(_("AudioDiskstream %1: cannot write to disk"), id()) << endmsg; + return -1; + } + + (*chan)->buf->increment_read_ptr (to_write); + (*chan)->curr_capture_cnt += to_write; + + if ((to_write == vector.len[0]) && (total > to_write) && (to_write < _chunk_frames) && !destructive()) { + + /* we wrote all of vector.len[0] but it wasn't an entire + disk_write_chunk_frames of data, so arrange for some part + of vector.len[1] to be flushed to disk as well. + */ + + to_write = min ((framecnt_t)(_chunk_frames - to_write), (framecnt_t) vector.len[1]); + + DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 additional write of %2\n", name(), to_write)); + + if ((*chan)->write_source->write (vector.buf[1], to_write) != to_write) { + error << string_compose(_("AudioDiskstream %1: cannot write to disk"), id()) << endmsg; + return -1; + } + + (*chan)->buf->increment_read_ptr (to_write); + (*chan)->curr_capture_cnt += to_write; + } + } + + /* MIDI*/ + + out: + return ret; + +} + +void +DiskWriter::reset_write_sources (bool mark_write_complete, bool /*force*/) +{ + ChannelList::iterator chan; + boost::shared_ptr<ChannelList> c = channels.reader(); + uint32_t n; + + if (!_session.writable() || !recordable()) { + return; + } + + capturing_sources.clear (); + + for (chan = c->begin(), n = 0; chan != c->end(); ++chan, ++n) { + + if (!destructive()) { + + if ((*chan)->write_source) { + + if (mark_write_complete) { + Source::Lock lock((*chan)->write_source->mutex()); + (*chan)->write_source->mark_streaming_write_completed (lock); + (*chan)->write_source->done_with_peakfile_writes (); + } + + if ((*chan)->write_source->removable()) { + (*chan)->write_source->mark_for_remove (); + (*chan)->write_source->drop_references (); + } + + (*chan)->write_source.reset (); + } + + use_new_write_source (DataType::AUDIO, n); + + if (record_enabled()) { + capturing_sources.push_back ((*chan)->write_source); + } + + } else { + + if ((*chan)->write_source == 0) { + use_new_write_source (DataType::AUDIO, n); + } + } + } + + if (_midi_write_source) { + if (mark_write_complete) { + Source::Lock lm(_midi_write_source->mutex()); + _midi_write_source->mark_streaming_write_completed (lm); + } + + use_new_write_source (DataType::MIDI); + + if (destructive() && !c->empty ()) { + + /* we now have all our write sources set up, so create the + playlist's single region. + */ + + if (_playlists[DataType::MIDI]->empty()) { + setup_destructive_playlist (); + } + } + } +} + +int +DiskWriter::use_new_write_source (DataType dt, uint32_t n) +{ + if (dt == DataType::MIDI) { + + _accumulated_capture_offset = 0; + _midi_write_source.reset(); + + try { + _midi_write_source = boost::dynamic_pointer_cast<SMFSource>( + _session.create_midi_source_for_session (write_source_name ())); + + if (!_midi_write_source) { + throw failed_constructor(); + } + } + + catch (failed_constructor &err) { + error << string_compose (_("%1:%2 new capture file not initialized correctly"), _name, n) << endmsg; + _midi_write_source.reset(); + return -1; + } + } else { + boost::shared_ptr<ChannelList> c = channels.reader(); + + if (!recordable()) { + return 1; + } + + if (n >= c->size()) { + error << string_compose (_("AudioDiskstream: channel %1 out of range"), n) << endmsg; + return -1; + } + + ChannelInfo* chan = (*c)[n]; + + try { + if ((chan->write_source = _session.create_audio_source_for_session ( + c->size(), write_source_name(), n, destructive())) == 0) { + throw failed_constructor(); + } + } + + catch (failed_constructor &err) { + error << string_compose (_("%1:%2 new capture file not initialized correctly"), _name, n) << endmsg; + chan->write_source.reset (); + return -1; + } + + /* do not remove destructive files even if they are empty */ + + chan->write_source->set_allow_remove_if_empty (!destructive()); + } + + return 0; +} + +void +DiskWriter::transport_stopped_wallclock (struct tm& when, time_t twhen, bool abort_capture) +{ + uint32_t buffer_position; + bool more_work = true; + int err = 0; + boost::shared_ptr<AudioRegion> region; + framecnt_t total_capture; + SourceList srcs; + SourceList::iterator src; + ChannelList::iterator chan; + vector<CaptureInfo*>::iterator ci; + boost::shared_ptr<ChannelList> c = channels.reader(); + uint32_t n = 0; + bool mark_write_completed = false; + + finish_capture (c); + + boost::shared_ptr<AudioPlaylist> pl = boost::dynamic_pointer_cast<AudioPlaylist> (_playlists[DataType::AUDIO]); + + /* butler is already stopped, but there may be work to do + to flush remaining data to disk. + */ + + while (more_work && !err) { + switch (do_flush (TransportContext, true)) { + case 0: + more_work = false; + break; + case 1: + break; + case -1: + error << string_compose(_("AudioDiskstream \"%1\": cannot flush captured data to disk!"), _name) << endmsg; + err++; + } + } + + /* XXX is there anything we can do if err != 0 ? */ + Glib::Threads::Mutex::Lock lm (capture_info_lock); + + if (capture_info.empty()) { + return; + } + + if (abort_capture) { + + if (destructive()) { + goto outout; + } + + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + + if ((*chan)->write_source) { + + (*chan)->write_source->mark_for_remove (); + (*chan)->write_source->drop_references (); + (*chan)->write_source.reset (); + } + + /* new source set up in "out" below */ + } + + goto out; + } + + for (total_capture = 0, ci = capture_info.begin(); ci != capture_info.end(); ++ci) { + total_capture += (*ci)->frames; + } + + /* figure out the name for this take */ + + for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) { + + boost::shared_ptr<AudioFileSource> s = (*chan)->write_source; + + if (s) { + srcs.push_back (s); + s->update_header (capture_info.front()->start, when, twhen); + s->set_captured_for (_name.val()); + s->mark_immutable (); + + if (Config->get_auto_analyse_audio()) { + Analyser::queue_source_for_analysis (s, true); + } + + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("newly captured source %1 length %2\n", s->path(), s->length (0))); + } + } + + if (!pl) { + goto midi; + } + + /* destructive tracks have a single, never changing region */ + + if (destructive()) { + + /* send a signal that any UI can pick up to do the right thing. there is + a small problem here in that a UI may need the peak data to be ready + for the data that was recorded and this isn't interlocked with that + process. this problem is deferred to the UI. + */ + + pl->LayeringChanged(); // XXX this may not get the UI to do the right thing + + } else { + + string whole_file_region_name; + whole_file_region_name = region_name_from_path (c->front()->write_source->name(), true); + + /* Register a new region with the Session that + describes the entire source. Do this first + so that any sub-regions will obviously be + children of this one (later!) + */ + + try { + PropertyList plist; + + plist.add (Properties::start, c->front()->write_source->last_capture_start_frame()); + plist.add (Properties::length, total_capture); + plist.add (Properties::name, whole_file_region_name); + boost::shared_ptr<Region> rx (RegionFactory::create (srcs, plist)); + rx->set_automatic (true); + rx->set_whole_file (true); + + region = boost::dynamic_pointer_cast<AudioRegion> (rx); + region->special_set_position (capture_info.front()->start); + } + + + catch (failed_constructor& err) { + error << string_compose(_("%1: could not create region for complete audio file"), _name) << endmsg; + /* XXX what now? */ + } + + _last_capture_sources.insert (_last_capture_sources.end(), srcs.begin(), srcs.end()); + + pl->clear_changes (); + pl->set_capture_insertion_in_progress (true); + pl->freeze (); + + const framepos_t preroll_off = _session.preroll_record_trim_len (); + for (buffer_position = c->front()->write_source->last_capture_start_frame(), ci = capture_info.begin(); ci != capture_info.end(); ++ci) { + + string region_name; + + RegionFactory::region_name (region_name, whole_file_region_name, false); + + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1 capture bufpos %5 start @ %2 length %3 add new region %4\n", + _name, (*ci)->start, (*ci)->frames, region_name, buffer_position)); + + try { + + PropertyList plist; + + plist.add (Properties::start, buffer_position); + plist.add (Properties::length, (*ci)->frames); + plist.add (Properties::name, region_name); + + boost::shared_ptr<Region> rx (RegionFactory::create (srcs, plist)); + region = boost::dynamic_pointer_cast<AudioRegion> (rx); + if (preroll_off > 0) { + region->trim_front (buffer_position + preroll_off); + } + } + + catch (failed_constructor& err) { + error << _("AudioDiskstream: could not create region for captured audio!") << endmsg; + continue; /* XXX is this OK? */ + } + + i_am_the_modifier++; + + pl->add_region (region, (*ci)->start + preroll_off, 1, non_layered()); + pl->set_layer (region, DBL_MAX); + i_am_the_modifier--; + + buffer_position += (*ci)->frames; + } + + pl->thaw (); + pl->set_capture_insertion_in_progress (false); + _session.add_command (new StatefulDiffCommand (pl)); + } + + mark_write_completed = true; + + out: + reset_write_sources (mark_write_completed); + + outout: + + for (ci = capture_info.begin(); ci != capture_info.end(); ++ci) { + delete *ci; + } + + capture_info.clear (); + capture_start_frame = 0; + + midi: + return; +} + +#if 0 // MIDI PART +void +DiskWriter::transport_stopped_wallclock (struct tm& /*when*/, time_t /*twhen*/, bool abort_capture) +{ + bool more_work = true; + int err = 0; + boost::shared_ptr<MidiRegion> region; + MidiRegion::SourceList srcs; + MidiRegion::SourceList::iterator src; + vector<CaptureInfo*>::iterator ci; + + finish_capture (); + + /* butler is already stopped, but there may be work to do + to flush remaining data to disk. + */ + + while (more_work && !err) { + switch (do_flush (TransportContext, true)) { + case 0: + more_work = false; + break; + case 1: + break; + case -1: + error << string_compose(_("MidiDiskstream \"%1\": cannot flush captured data to disk!"), _name) << endmsg; + err++; + } + } + + /* XXX is there anything we can do if err != 0 ? */ + Glib::Threads::Mutex::Lock lm (capture_info_lock); + + if (capture_info.empty()) { + goto no_capture_stuff_to_do; + } + + if (abort_capture) { + + if (_write_source) { + _write_source->mark_for_remove (); + _write_source->drop_references (); + _write_source.reset(); + } + + /* new source set up in "out" below */ + + } else { + + framecnt_t total_capture = 0; + for (ci = capture_info.begin(); ci != capture_info.end(); ++ci) { + total_capture += (*ci)->frames; + } + + if (_write_source->length (capture_info.front()->start) != 0) { + + /* phew, we have data */ + + Source::Lock source_lock(_write_source->mutex()); + + /* figure out the name for this take */ + + srcs.push_back (_write_source); + + _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_midi_streaming_write_completed (source_lock, Evoral::Sequence<Evoral::Beats>::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 + from the audio situation, where the source at this point + must be considered immutable. luckily, we can rely on + MidiSource::mark_streaming_write_completed() to have + already done the necessary work for that. + */ + + string whole_file_region_name; + whole_file_region_name = region_name_from_path (_write_source->name(), true); + + /* Register a new region with the Session that + describes the entire source. Do this first + so that any sub-regions will obviously be + children of this one (later!) + */ + + try { + PropertyList plist; + + plist.add (Properties::name, whole_file_region_name); + plist.add (Properties::whole_file, true); + plist.add (Properties::automatic, true); + plist.add (Properties::start, 0); + plist.add (Properties::length, total_capture); + plist.add (Properties::layer, 0); + + boost::shared_ptr<Region> rx (RegionFactory::create (srcs, plist)); + + region = boost::dynamic_pointer_cast<MidiRegion> (rx); + region->special_set_position (capture_info.front()->start); + } + + + catch (failed_constructor& err) { + error << string_compose(_("%1: could not create region for complete midi file"), _name) << endmsg; + /* XXX what now? */ + } + + _last_capture_sources.insert (_last_capture_sources.end(), srcs.begin(), srcs.end()); + + _playlist->clear_changes (); + _playlist->freeze (); + + /* Session frame time of the initial capture in this pass, which is where the source starts */ + framepos_t initial_capture = 0; + if (!capture_info.empty()) { + initial_capture = capture_info.front()->start; + } + + const framepos_t preroll_off = _session.preroll_record_trim_len (); + for (ci = capture_info.begin(); ci != capture_info.end(); ++ci) { + + string region_name; + + 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 { + PropertyList plist; + + /* start of this region is the offset between the start of its capture and the start of the whole pass */ + plist.add (Properties::start, (*ci)->start - initial_capture); + plist.add (Properties::length, (*ci)->frames); + plist.add (Properties::length_beats, converter.from((*ci)->frames).to_double()); + plist.add (Properties::name, region_name); + + boost::shared_ptr<Region> rx (RegionFactory::create (srcs, plist)); + region = boost::dynamic_pointer_cast<MidiRegion> (rx); + if (preroll_off > 0) { + region->trim_front ((*ci)->start - initial_capture + preroll_off); + } + } + + catch (failed_constructor& err) { + error << _("MidiDiskstream: could not create region for captured midi!") << endmsg; + continue; /* XXX is this OK? */ + } + + // cerr << "add new region, buffer position = " << buffer_position << " @ " << (*ci)->start << endl; + + i_am_the_modifier++; + _playlist->add_region (region, (*ci)->start + preroll_off); + i_am_the_modifier--; + } + + _playlist->thaw (); + _session.add_command (new StatefulDiffCommand(_playlist)); + + } else { + + /* No data was recorded, so this capture will + effectively be aborted; do the same as we + do for an explicit abort. + */ + + if (_write_source) { + _write_source->mark_for_remove (); + _write_source->drop_references (); + _write_source.reset(); + } + } + + } + + use_new_write_source (0); + + for (ci = capture_info.begin(); ci != capture_info.end(); ++ci) { + delete *ci; + } + + capture_info.clear (); + capture_start_frame = 0; + + no_capture_stuff_to_do: + + reset_tracker (); +} +#endif + +void +DiskWriter::transport_looped (framepos_t transport_frame) +{ + if (was_recording) { + // all we need to do is finish this capture, with modified capture length + boost::shared_ptr<ChannelList> c = channels.reader(); + + finish_capture (c); + + // 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 (recordable() && destructive()) { + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + + RingBufferNPT<CaptureTransition>::rw_vector transvec; + (*chan)->capture_transition_buf->get_write_vector(&transvec); + + if (transvec.len[0] > 0) { + transvec.buf[0]->type = CaptureStart; + transvec.buf[0]->capture_val = capture_start_frame; + (*chan)->capture_transition_buf->increment_write_ptr(1); + } + else { + // bad! + fatal << X_("programming error: capture_transition_buf is full on rec loop! inconceivable!") + << endmsg; + } + } + } + + } + + /* 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) { + g_atomic_int_add(const_cast<gint*> (&_num_captured_loops), 1); + } +} + +void +DiskWriter::setup_destructive_playlist () +{ + SourceList srcs; + boost::shared_ptr<ChannelList> c = channels.reader(); + + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { + srcs.push_back ((*chan)->write_source); + } + + /* a single full-sized region */ + + assert (!srcs.empty ()); + + PropertyList plist; + plist.add (Properties::name, _name.val()); + plist.add (Properties::start, 0); + plist.add (Properties::length, max_framepos - srcs.front()->natural_position()); + + boost::shared_ptr<Region> region (RegionFactory::create (srcs, plist)); + _playlists[DataType::AUDIO]->add_region (region, srcs.front()->natural_position()); + + /* apply region properties and update write sources */ + use_destructive_playlist(); +} + +void +DiskWriter::use_destructive_playlist () +{ + /* this is called from the XML-based constructor or ::set_destructive. when called, + we already have a playlist and a region, but we need to + set up our sources for write. we use the sources associated + with the (presumed single, full-extent) region. + */ + + boost::shared_ptr<Region> rp; + { + const RegionList& rl (_playlists[DataType::AUDIO]->region_list_property().rlist()); + if (rl.size() > 0) { + /* this can happen when dragging a region onto a tape track */ + assert((rl.size() == 1)); + rp = rl.front(); + } + } + + if (!rp) { + reset_write_sources (false, true); + return; + } + + boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (rp); + + if (region == 0) { + throw failed_constructor(); + } + + /* be sure to stretch the region out to the maximum length (non-musical)*/ + + region->set_length (max_framepos - region->position(), 0); + + uint32_t n; + ChannelList::iterator chan; + boost::shared_ptr<ChannelList> c = channels.reader(); + + for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) { + (*chan)->write_source = boost::dynamic_pointer_cast<AudioFileSource>(region->source (n)); + assert((*chan)->write_source); + (*chan)->write_source->set_allow_remove_if_empty (false); + + /* this might be false if we switched modes, so force it */ + +#ifdef XXX_OLD_DESTRUCTIVE_API_XXX + (*chan)->write_source->set_destructive (true); +#else + // should be set when creating the source or loading the state + assert ((*chan)->write_source->destructive()); +#endif + } + + /* the source list will never be reset for a destructive track */ +} |