X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=libs%2Fardour%2Faudio_diskstream.cc;h=330b9d582acda0367d509993f54c0b1b2b98e339;hb=8e48655981ae4b7c64d2a55e01f903eed01b6727;hp=30dd556735988f9c817e372617ea3014259eb826;hpb=6890c43dce14b05763daeb1e57f1db5634daa6b0;p=ardour.git diff --git a/libs/ardour/audio_diskstream.cc b/libs/ardour/audio_diskstream.cc index 30dd556735..330b9d582a 100644 --- a/libs/ardour/audio_diskstream.cc +++ b/libs/ardour/audio_diskstream.cc @@ -16,7 +16,6 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include #include #include #include @@ -27,54 +26,48 @@ #include #include #include -#include -#include +#include "pbd/gstdio_compat.h" #include "pbd/error.h" -#include #include "pbd/xml++.h" #include "pbd/memento_command.h" #include "pbd/enumwriter.h" #include "pbd/stateful_diff_command.h" #include "ardour/analyser.h" -#include "ardour/ardour.h" #include "ardour/audio_buffer.h" #include "ardour/audio_diskstream.h" #include "ardour/audio_port.h" #include "ardour/audioengine.h" #include "ardour/audiofilesource.h" - #include "ardour/audioplaylist.h" #include "ardour/audioregion.h" #include "ardour/butler.h" -#include "ardour/configuration.h" -#include "ardour/cycle_timer.h" #include "ardour/debug.h" #include "ardour/io.h" #include "ardour/playlist_factory.h" +#include "ardour/profile.h" #include "ardour/region_factory.h" -#include "ardour/send.h" #include "ardour/session.h" +#include "ardour/session_playlists.h" +#include "ardour/sndfile_helpers.h" #include "ardour/source_factory.h" +#include "ardour/track.h" +#include "ardour/types.h" #include "ardour/utils.h" -#include "ardour/session_playlists.h" -#include "ardour/route.h" -#include "i18n.h" +#include "pbd/i18n.h" #include using namespace std; using namespace ARDOUR; using namespace PBD; -size_t AudioDiskstream::_working_buffers_size = 0; Sample* AudioDiskstream::_mixdown_buffer = 0; gain_t* AudioDiskstream::_gain_buffer = 0; AudioDiskstream::AudioDiskstream (Session &sess, const string &name, Diskstream::Flag flag) : Diskstream(sess, name, flag) - , deprecated_io_node(NULL) , channels (new ChannelList) { /* prevent any write sources from being created */ @@ -82,11 +75,14 @@ AudioDiskstream::AudioDiskstream (Session &sess, const string &name, Diskstream: in_set_state = true; use_new_playlist (); in_set_state = false; + + if (flag & Destructive) { + use_destructive_playlist (); + } } AudioDiskstream::AudioDiskstream (Session& sess, const XMLNode& node) : Diskstream(sess, node) - , deprecated_io_node(NULL) , channels (new ChannelList) { in_set_state = true; @@ -132,18 +128,18 @@ AudioDiskstream::~AudioDiskstream () } channels.flush (); - - delete deprecated_io_node; } void AudioDiskstream::allocate_working_buffers() { - assert(disk_io_frames() > 0); - - _working_buffers_size = disk_io_frames(); - _mixdown_buffer = new Sample[_working_buffers_size]; - _gain_buffer = new gain_t[_working_buffers_size]; + /* with varifill buffer refilling, we compute the read size in bytes (to optimize + for disk i/o bandwidth) and then convert back into samples. These buffers + need to reflect the maximum size we could use, which is 4MB reads, or 2M samples + using 16 bit samples. + */ + _mixdown_buffer = new Sample[2*1048576]; + _gain_buffer = new gain_t[2*1048576]; } void @@ -151,7 +147,6 @@ AudioDiskstream::free_working_buffers() { delete [] _mixdown_buffer; delete [] _gain_buffer; - _working_buffers_size = 0; _mixdown_buffer = 0; _gain_buffer = 0; } @@ -159,14 +154,21 @@ AudioDiskstream::free_working_buffers() void AudioDiskstream::non_realtime_input_change () { + bool need_write_sources = false; + { - Glib::Mutex::Lock lm (state_lock); + Glib::Threads::Mutex::Lock lm (state_lock); if (input_change_pending.type == IOChange::NoChange) { return; } - { + boost::shared_ptr cr = channels.reader(); + if (!cr->empty() && !cr->front()->write_source) { + need_write_sources = true; + } + + if (input_change_pending.type & IOChange::ConfigurationChanged) { RCUWriter writer (channels); boost::shared_ptr c = writer.get_copy(); @@ -177,15 +179,13 @@ AudioDiskstream::non_realtime_input_change () } else if (_io->n_ports().n_audio() < _n_channels.n_audio()) { remove_channel_from (c, _n_channels.n_audio() - _io->n_ports().n_audio()); } - } - get_input_sources (); - set_capture_offset (); + need_write_sources = true; + } - if (first_input_change) { - set_align_style (_persistent_alignment_style); - first_input_change = false; - } else { + if (input_change_pending.type & IOChange::ConnectionsChanged) { + get_input_sources (); + set_capture_offset (); set_align_style_from_io (); } @@ -194,9 +194,9 @@ AudioDiskstream::non_realtime_input_change () /* implicit unlock */ } - /* reset capture files */ - - reset_write_sources (false); + if (need_write_sources) { + reset_write_sources (false); + } /* now refill channel buffers */ @@ -213,9 +213,9 @@ AudioDiskstream::non_realtime_locate (framepos_t location) /* now refill channel buffers */ if (speed() != 1.0f || speed() != -1.0f) { - seek ((framepos_t) (location * (double) speed())); + seek ((framepos_t) (location * (double) speed()), true); } else { - seek (location); + seek (location, true); } } @@ -233,16 +233,13 @@ AudioDiskstream::get_input_sources () connections.clear (); - if (_io->nth (n)->get_connections (connections) == 0) { - - if ((*chan)->source) { + if ((_io->nth (n).get()) && (_io->nth (n)->get_connections (connections) == 0)) { + if (!(*chan)->source.name.empty()) { // _source->disable_metering (); } - - (*chan)->source = 0; - + (*chan)->source.name = string(); } else { - (*chan)->source = dynamic_cast(_session.engine().get_port_by_name (connections[0]) ); + (*chan)->source.name = connections[0]; } } } @@ -292,7 +289,6 @@ AudioDiskstream::use_new_playlist () if ((playlist = boost::dynamic_pointer_cast (PlaylistFactory::create (DataType::AUDIO, _session, newname, hidden()))) != 0) { - playlist->set_orig_diskstream_id (id()); return use_playlist (playlist); } else { @@ -320,7 +316,6 @@ AudioDiskstream::use_copy_playlist () newname = Playlist::bump_name (_playlist->name(), _session); if ((playlist = boost::dynamic_pointer_cast(PlaylistFactory::create (audio_playlist(), newname))) != 0) { - playlist->set_orig_diskstream_id (id()); return use_playlist (playlist); } else { return -1; @@ -344,10 +339,13 @@ AudioDiskstream::setup_destructive_playlist () PropertyList plist; plist.add (Properties::name, _name.val()); plist.add (Properties::start, 0); - plist.add (Properties::length, max_framepos - (max_framepos - srcs.front()->natural_position())); + plist.add (Properties::length, max_framepos - srcs.front()->natural_position()); boost::shared_ptr region (RegionFactory::create (srcs, plist)); _playlist->add_region (region, srcs.front()->natural_position()); + + /* apply region properties and update write sources */ + use_destructive_playlist(); } void @@ -359,7 +357,14 @@ AudioDiskstream::use_destructive_playlist () with the (presumed single, full-extent) region. */ - boost::shared_ptr rp = _playlist->find_next_region (_session.current_start_frame(), Start, 1); + boost::shared_ptr rp; + { + const RegionList& rl (_playlist->region_list_property().rlist()); + if (rl.size() > 0) { + assert((rl.size() == 1)); + rp = rl.front(); + } + } if (!rp) { reset_write_sources (false, true); @@ -372,9 +377,9 @@ AudioDiskstream::use_destructive_playlist () throw failed_constructor(); } - /* be sure to stretch the region out to the maximum length */ + /* be sure to stretch the region out to the maximum length (non-musical)*/ - region->set_length (max_framepos - region->position(), this); + region->set_length (max_framepos - region->position(), 0); uint32_t n; ChannelList::iterator chan; @@ -400,12 +405,12 @@ AudioDiskstream::prepare_record_status(framepos_t capture_start_frame) boost::shared_ptr c = channels.reader(); for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - RingBufferNPT::rw_vector transvec; - (*chan)->capture_transition_buf->get_write_vector(&transvec); + RingBufferNPT::rw_vector transitions; + (*chan)->capture_transition_buf->get_write_vector (&transitions); - if (transvec.len[0] > 0) { - transvec.buf[0]->type = CaptureStart; - transvec.buf[0]->capture_val = capture_start_frame; + if (transitions.len[0] > 0) { + transitions.buf[0]->type = CaptureStart; + transitions.buf[0]->capture_val = capture_start_frame; (*chan)->capture_transition_buf->increment_write_ptr(1); } else { // bad! @@ -416,18 +421,27 @@ AudioDiskstream::prepare_record_status(framepos_t capture_start_frame) } } + +/** Do some record stuff [not described in this comment!] + * + * Also: + * - Setup playback_distance with the nframes, or nframes adjusted + * for current varispeed, if appropriate. + * - Setup current_playback_buffer in each ChannelInfo to point to data + * that someone can read playback_distance worth of data from. + */ int -AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool can_record, bool rec_monitors_input, bool& need_butler) +AudioDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t nframes, framecnt_t& playback_distance, bool need_disk_signal) { uint32_t n; boost::shared_ptr c = channels.reader(); ChannelList::iterator chan; - int ret = -1; framecnt_t rec_offset = 0; framecnt_t rec_nframes = 0; bool collect_playback = false; + bool can_record = _session.actively_recording (); - playback_distance = 0; + playback_distance = 0; if (!_io || !_io->active()) { return 0; @@ -439,9 +453,9 @@ AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool ca return 0; } - Glib::Mutex::Lock sm (state_lock, Glib::TRY_LOCK); + Glib::Threads::Mutex::Lock sm (state_lock, Glib::Threads::TRY_LOCK); - if (!sm.locked()) { + if (!sm.locked()) { return 1; } @@ -452,31 +466,27 @@ AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool ca (*chan)->current_playback_buffer = 0; } - /* two conditions to test for here: - - A: this track is rec-enabled, and the session has confirmed that we can record - B: this track is rec-enabled, has been recording, and we are set up for auto-punch-in + // Safeguard against situations where process() goes haywire when autopunching + // and last_recordable_frame < first_recordable_frame - The second test is necessary to capture the extra material that arrives AFTER the transport - frame has left the punch range (which will cause the "can_record" argument to be false). - */ + if (last_recordable_frame < first_recordable_frame) { + last_recordable_frame = max_framepos; + } + if (record_enabled()) { - // Safeguard against situations where process() goes haywire when autopunching - // and last_recordable_frame < first_recordable_frame + 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 + // XXX also, first_recordable_frame & last_recordable_frame may both be == max_framepos: coverage() will return OverlapNone in that case. Is thak OK? + calculate_record_range (ot, transport_frame, nframes, rec_nframes, rec_offset); - if (last_recordable_frame < first_recordable_frame) { - last_recordable_frame = max_framepos; - } - - OverlapType ot = coverage (first_recordable_frame, last_recordable_frame, transport_frame, transport_frame + nframes); + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: this time record %2 of %3 frames, offset %4\n", _name, rec_nframes, nframes, rec_offset)); - calculate_record_range (ot, transport_frame, nframes, rec_nframes, rec_offset); - - if (rec_nframes && !was_recording) { - capture_captured = 0; - was_recording = true; - } + if (rec_nframes && !was_recording) { + capture_captured = 0; + was_recording = true; + } + } if (can_record && !_last_capture_sources.empty()) { _last_capture_sources.clear (); @@ -507,25 +517,28 @@ AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool ca for recording, and use rec_offset */ - AudioPort* const ap = _io->audio (n); + boost::shared_ptr const ap = _io->audio (n); assert(ap); assert(rec_nframes <= (framecnt_t) ap->get_audio_buffer(nframes).capacity()); - memcpy (chaninfo->current_capture_buffer, ap->get_audio_buffer (nframes).data(rec_offset), sizeof (Sample) * rec_nframes); + Sample *buf = bufs.get_audio (n).data(rec_offset); + memcpy (chaninfo->current_capture_buffer, buf, sizeof (Sample) * rec_nframes); } else { framecnt_t total = chaninfo->capture_vector.len[0] + chaninfo->capture_vector.len[1]; if (rec_nframes > total) { + DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 overrun in %2, rec_nframes = %3 total space = %4\n", + DEBUG_THREAD_SELF, name(), rec_nframes, total)); DiskOverrun (); - goto out; + return -1; } - AudioPort* const ap = _io->audio (n); + boost::shared_ptr const ap = _io->audio (n); assert(ap); - Sample* buf = ap->get_audio_buffer(nframes).data(); + Sample *buf = bufs.get_audio (n).data(rec_offset); framecnt_t first = chaninfo->capture_vector.len[0]; memcpy (chaninfo->capture_wrap_buffer, buf, sizeof (Sample) * first); @@ -540,7 +553,7 @@ AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool ca } else { if (was_recording) { - finish_capture (rec_monitors_input, c); + finish_capture (c); } } @@ -585,7 +598,7 @@ AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool ca collect_playback = true; } - if (collect_playback) { + if ((_track->monitoring_state () & MonitoringDisk) || collect_playback) { /* we're doing playback */ @@ -593,8 +606,8 @@ AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool ca /* no varispeed playback if we're recording, because the output .... TBD */ - if (rec_nframes == 0 && _actual_speed != 1.0f) { - necessary_samples = (framecnt_t) floor ((nframes * fabs (_actual_speed))) + 1; + if (rec_nframes == 0 && _actual_speed != 1.0) { + necessary_samples = (framecnt_t) ceil ((nframes * fabs (_actual_speed))) + 2; } else { necessary_samples = nframes; } @@ -605,12 +618,17 @@ AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool ca n = 0; + /* Setup current_playback_buffer in each ChannelInfo to point to data that someone + can read necessary_samples (== nframes at a transport speed of 1) worth of data + from right now. + */ + for (chan = c->begin(); chan != c->end(); ++chan, ++n) { ChannelInfo* chaninfo (*chan); if (necessary_samples <= (framecnt_t) chaninfo->playback_vector.len[0]) { - + /* There are enough samples in the first part of the ringbuffer */ chaninfo->current_playback_buffer = chaninfo->playback_vector.buf[0]; } else { @@ -619,14 +637,26 @@ AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool ca if (necessary_samples > total) { cerr << _name << " Need " << necessary_samples << " total = " << total << endl; cerr << "underrun for " << _name << endl; + DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 underrun in %2, rec_nframes = %3 total space = %4\n", + DEBUG_THREAD_SELF, name(), rec_nframes, total)); DiskUnderrun (); - goto out; + return -1; } else { + /* We have enough samples, but not in one lump. Coalesce the two parts + into one in playback_wrap_buffer in our ChannelInfo, and specify that + as our current_playback_buffer. + */ + + assert(wrap_buffer_size >= necessary_samples); + + /* Copy buf[0] from playback_buf */ memcpy ((char *) chaninfo->playback_wrap_buffer, chaninfo->playback_vector.buf[0], chaninfo->playback_vector.len[0] * sizeof (Sample)); + + /* Copy buf[1] from playback_buf */ memcpy (chaninfo->playback_wrap_buffer + chaninfo->playback_vector.len[0], chaninfo->playback_vector.buf[1], (necessary_samples - chaninfo->playback_vector.len[0]) @@ -638,45 +668,100 @@ AudioDiskstream::process (framepos_t transport_frame, pframes_t nframes, bool ca } if (rec_nframes == 0 && _actual_speed != 1.0f && _actual_speed != -1.0f) { - process_varispeed_playback(nframes, c); + + interpolation.set_speed (_target_speed); + + int channel = 0; + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) { + ChannelInfo* chaninfo (*chan); + + playback_distance = interpolation.interpolate ( + channel, nframes, chaninfo->current_playback_buffer, chaninfo->speed_buffer); + + chaninfo->current_playback_buffer = chaninfo->speed_buffer; + } + } else { playback_distance = nframes; } _speed = _target_speed; + } - } + if (need_disk_signal) { - ret = 0; + /* copy data over to buffer set */ - if (commit (nframes)) { - need_butler = true; - } + size_t n_buffers = bufs.count().n_audio(); + size_t n_chans = c->size(); + gain_t scaling = 1.0f; - out: - return ret; -} + if (n_chans > n_buffers) { + scaling = ((float) n_buffers)/n_chans; + } -void -AudioDiskstream::process_varispeed_playback (pframes_t nframes, boost::shared_ptr c) -{ - ChannelList::iterator chan; + for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) { - interpolation.set_speed (_target_speed); + AudioBuffer& buf (bufs.get_audio (n%n_buffers)); + ChannelInfo* chaninfo (*chan); - int channel = 0; - for (chan = c->begin(); chan != c->end(); ++chan, ++channel) { - ChannelInfo* chaninfo (*chan); + if (n < n_chans) { + if (scaling != 1.0f) { + buf.read_from_with_gain (chaninfo->current_playback_buffer, nframes, scaling); + } else { + buf.read_from (chaninfo->current_playback_buffer, nframes); + } + } else { + if (scaling != 1.0f) { + buf.accumulate_with_gain_from (chaninfo->current_playback_buffer, nframes, scaling); + } else { + buf.accumulate_from (chaninfo->current_playback_buffer, nframes); + } + } + } + + /* leave the MIDI count alone */ + ChanCount cnt (DataType::AUDIO, n_chans); + cnt.set (DataType::MIDI, bufs.count().n_midi()); + bufs.set_count (cnt); + + /* extra buffers will already be silent, so leave them alone */ + } + + return 0; +} + +frameoffset_t +AudioDiskstream::calculate_playback_distance (pframes_t nframes) +{ + frameoffset_t playback_distance = nframes; - playback_distance = interpolation.interpolate ( - channel, nframes, chaninfo->current_playback_buffer, chaninfo->speed_buffer); + if (record_enabled()) { + playback_distance = nframes; + } else if (_actual_speed != 1.0f && _actual_speed != -1.0f) { + interpolation.set_speed (_target_speed); + boost::shared_ptr c = channels.reader(); + int channel = 0; + for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++channel) { + playback_distance = interpolation.interpolate (channel, nframes, NULL, NULL); + } + } else { + playback_distance = nframes; + } - chaninfo->current_playback_buffer = chaninfo->speed_buffer; + if (_actual_speed < 0.0) { + return -playback_distance; + } else { + return playback_distance; } } +/** Update various things including playback_sample, read pointer on each channel's playback_buf + * and write pointer on each channel's capture_buf. Also wout whether the butler is needed. + * @return true if the butler is required. + */ bool -AudioDiskstream::commit (framecnt_t /* nframes */) +AudioDiskstream::commit (framecnt_t playback_distance) { bool need_butler = false; @@ -702,13 +787,14 @@ AudioDiskstream::commit (framecnt_t /* nframes */) if (adjust_capture_position != 0) { capture_captured += adjust_capture_position; + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1 now captured %2 (by %3)\n", name(), capture_captured, adjust_capture_position)); adjust_capture_position = 0; } - + if (c->empty()) { return false; } - + if (_slaved) { if (_io && _io->active()) { need_butler = c->front()->playback_buf->write_space() >= c->front()->playback_buf->bufsize() / 2; @@ -717,10 +803,10 @@ AudioDiskstream::commit (framecnt_t /* nframes */) } } else { if (_io && _io->active()) { - need_butler = ((framecnt_t) c->front()->playback_buf->write_space() >= disk_io_chunk_frames) - || ((framecnt_t) c->front()->capture_buf->read_space() >= disk_io_chunk_frames); + need_butler = ((framecnt_t) c->front()->playback_buf->write_space() >= disk_read_chunk_frames) + || ((framecnt_t) c->front()->capture_buf->read_space() >= disk_write_chunk_frames); } else { - need_butler = ((framecnt_t) c->front()->capture_buf->read_space() >= disk_io_chunk_frames); + need_butler = ((framecnt_t) c->front()->capture_buf->read_space() >= disk_write_chunk_frames); } } @@ -750,7 +836,7 @@ AudioDiskstream::overwrite_existing_buffers () _pending_overwrite = false; return 0; } - + Sample* mixdown_buffer; float* gain_buffer; int ret = -1; @@ -764,7 +850,9 @@ AudioDiskstream::overwrite_existing_buffers () mixdown_buffer = new Sample[size]; gain_buffer = new float[size]; - /* reduce size so that we can fill the buffer correctly. */ + /* reduce size so that we can fill the buffer correctly (ringbuffers + can only handle size-1, otherwise they appear to be empty) + */ size--; uint32_t n=0; @@ -788,9 +876,9 @@ AudioDiskstream::overwrite_existing_buffers () framecnt_t to_read = size - overwrite_offset; - if (read ((*chan)->playback_buf->buffer() + overwrite_offset, mixdown_buffer, gain_buffer, start, to_read, *chan, n, reversed)) { + if (read ((*chan)->playback_buf->buffer() + overwrite_offset, mixdown_buffer, gain_buffer, start, to_read, n, reversed)) { error << string_compose(_("AudioDiskstream %1: when refilling, cannot read %2 from playlist at frame %3"), - _id, size, playback_sample) << endmsg; + id(), size, playback_sample) << endmsg; goto out; } @@ -798,10 +886,9 @@ AudioDiskstream::overwrite_existing_buffers () cnt -= to_read; - if (read ((*chan)->playback_buf->buffer(), mixdown_buffer, gain_buffer, - start, cnt, *chan, n, reversed)) { + if (read ((*chan)->playback_buf->buffer(), mixdown_buffer, gain_buffer, start, cnt, n, reversed)) { error << string_compose(_("AudioDiskstream %1: when refilling, cannot read %2 from playlist at frame %3"), - _id, size, playback_sample) << endmsg; + id(), size, playback_sample) << endmsg; goto out; } } @@ -824,7 +911,7 @@ AudioDiskstream::seek (framepos_t frame, bool complete_refill) ChannelList::iterator chan; boost::shared_ptr c = channels.reader(); - Glib::Mutex::Lock lm (state_lock); + Glib::Threads::Mutex::Lock lm (state_lock); for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) { (*chan)->playback_buf->reset (); @@ -841,9 +928,15 @@ AudioDiskstream::seek (framepos_t frame, bool complete_refill) file_frame = frame; if (complete_refill) { - while ((ret = do_refill_with_alloc ()) > 0) ; + /* call _do_refill() to refill the entire buffer, using + the largest reads possible. + */ + while ((ret = do_refill_with_alloc (false)) > 0) ; } else { - ret = do_refill_with_alloc (); + /* call _do_refill() to refill just one chunk, and then + return. + */ + ret = do_refill_with_alloc (true); } return ret; @@ -870,7 +963,7 @@ AudioDiskstream::internal_playback_seek (framecnt_t distance) boost::shared_ptr c = channels.reader(); for (chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->playback_buf->increment_read_ptr (distance); + (*chan)->playback_buf->increment_read_ptr (::llabs(distance)); } if (first_recordable_frame < max_framepos) { @@ -881,10 +974,17 @@ AudioDiskstream::internal_playback_seek (framecnt_t distance) return 0; } +/** Read some data for 1 channel from our playlist into a buffer. + * @param buf Buffer to write to. + * @param start Session frame to start reading from; updated to where we end up + * after the read. + * @param cnt Count of samples to read. + * @param reversed true if we are running backwards, otherwise false. + */ int -AudioDiskstream::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, +AudioDiskstream::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, framepos_t& start, framecnt_t cnt, - ChannelInfo* /*channel_info*/, int channel, bool reversed) + int channel, bool reversed) { framecnt_t this_read = 0; bool reloop = false; @@ -918,25 +1018,25 @@ AudioDiskstream::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, */ 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 << " loopstart: " << loop_start << " loopend: " << loop_end << endl; } if (reversed) { start -= cnt; } - + + /* We need this while loop in case we hit a loop boundary, in which case our read from + the playlist must be split into more than one section. + */ + while (cnt) { /* take any loop into account. we can't read past the end of the loop. */ if (loc && (loop_end - start < cnt)) { this_read = loop_end - start; - //cerr << "reloop true: thisread: " << this_read << " cnt: " << cnt << endl; reloop = true; } else { reloop = false; @@ -950,13 +1050,11 @@ AudioDiskstream::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, this_read = min(cnt,this_read); if (audio_playlist()->read (buf+offset, mixdown_buffer, gain_buffer, start, this_read, channel) != this_read) { - error << string_compose(_("AudioDiskstream %1: cannot read %2 from playlist at frame %3"), _id, this_read, + error << string_compose(_("AudioDiskstream %1: cannot read %2 from playlist at frame %3"), id(), this_read, start) << endmsg; return -1; } - _read_data_count = _playlist->read_data_count(); - if (reversed) { swap_by_ptr (buf, buf + this_read - 1); @@ -980,12 +1078,18 @@ AudioDiskstream::read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, } int -AudioDiskstream::do_refill_with_alloc () +AudioDiskstream::_do_refill_with_alloc (bool partial_fill) { - Sample* mix_buf = new Sample[disk_io_chunk_frames]; - float* gain_buf = new float[disk_io_chunk_frames]; + /* We limit disk reads to at most 4MB chunks, which with floating point + samples would be 1M samples. But we might use 16 or 14 bit samples, + in which case 4MB is more samples than that. Therefore size this for + the smallest sample value .. 4MB = 2M samples (16 bit). + */ - int ret = _do_refill(mix_buf, gain_buf); + Sample* mix_buf = new Sample[2*1048576]; + float* gain_buf = new float[2*1048576]; + + int ret = _do_refill (mix_buf, gain_buf, (partial_fill ? disk_read_chunk_frames : 0)); delete [] mix_buf; delete [] gain_buf; @@ -993,13 +1097,22 @@ AudioDiskstream::do_refill_with_alloc () return ret; } +/** Get some more data from disk and put it in our channels' playback_bufs, + * if there is suitable space in them. + * + * If fill_level is non-zero, then we will refill the buffer so that there is + * still at least fill_level samples of space left to be filled. This is used + * after locates so that we do not need to wait to fill the entire buffer. + * + */ + int -AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) +AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer, framecnt_t fill_level) { int32_t ret = 0; framecnt_t to_read; RingBufferNPT::rw_vector vector; - bool reversed = (_visible_speed * _session.transport_speed()) < 0.0f; + bool const reversed = (_visible_speed * _session.transport_speed()) < 0.0f; framecnt_t total_space; framecnt_t zero_fill; uint32_t chan_n; @@ -1007,6 +1120,14 @@ AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) boost::shared_ptr c = channels.reader(); framecnt_t ts; + /* do not read from disk while session is marked as Loading, to avoid + useless redundant I/O. + */ + + if (_session.state_of_the_state() & Session::Loading) { + return 0; + } + if (c->empty()) { return 0; } @@ -1022,26 +1143,31 @@ AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) c->front()->playback_buf->get_write_vector (&vector); if ((total_space = vector.len[0] + vector.len[1]) == 0) { + /* nowhere to write to */ return 0; } - /* 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 (total_space >= (_slaved?3:2) * disk_io_chunk_frames) { - ret = 1; + if (fill_level) { + if (fill_level < total_space) { + total_space -= fill_level; + } else { + /* we can't do anything with it */ + fill_level = 0; + } } /* if we're running close to normal speed and there isn't enough - space to do disk_io_chunk_frames of I/O, then don't bother. + space to do disk_read_chunk_frames of I/O, then don't bother. at higher speeds, just do it because the sync between butler and audio thread may not be good enough. + + Note: it is a design assumption that disk_read_chunk_frames is smaller + than the playback buffer size, so this check should never trip when + the playback buffer is empty. */ - if ((total_space < disk_io_chunk_frames) && fabs (_actual_speed) < 2.0f) { + if ((total_space < disk_read_chunk_frames) && fabs (_actual_speed) < 2.0f) { return 0; } @@ -1054,10 +1180,6 @@ AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) return 0; } - /* never do more than disk_io_chunk_frames worth of disk input per call (limit doesn't apply for memset) */ - - total_space = min (disk_io_chunk_frames, total_space); - if (reversed) { if (file_frame == 0) { @@ -1085,7 +1207,6 @@ AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) zero_fill = total_space - file_frame; total_space = file_frame; - file_frame = 0; } else { @@ -1125,6 +1246,30 @@ AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) framepos_t file_frame_tmp = 0; + /* total_space is in samples. We want to optimize read sizes in various sizes using bytes */ + + const size_t bits_per_sample = format_data_width (_session.config.get_native_file_data_format()); + size_t total_bytes = total_space * bits_per_sample / 8; + + /* chunk size range is 256kB to 4MB. Bigger is faster in terms of MB/sec, but bigger chunk size always takes longer + */ + size_t byte_size_for_read = max ((size_t) (256 * 1024), min ((size_t) (4 * 1048576), total_bytes)); + + /* find nearest (lower) multiple of 16384 */ + + byte_size_for_read = (byte_size_for_read / 16384) * 16384; + + /* now back to samples */ + + framecnt_t samples_to_read = byte_size_for_read / (bits_per_sample / 8); + + //cerr << name() << " will read " << byte_size_for_read << " out of total bytes " << total_bytes << " in buffer of " + // << c->front()->playback_buf->bufsize() * bits_per_sample / 8 << " bps = " << bits_per_sample << endl; + // cerr << name () << " read samples = " << samples_to_read << " out of total space " << total_space << " in buffer of " << c->front()->playback_buf->bufsize() << " samples\n"; + + // uint64_t before = g_get_monotonic_time (); + // uint64_t elapsed; + for (chan_n = 0, i = c->begin(); i != c->end(); ++i, ++chan_n) { ChannelInfo* chan (*i); @@ -1134,14 +1279,14 @@ AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) chan->playback_buf->get_write_vector (&vector); - if ((framecnt_t) vector.len[0] > disk_io_chunk_frames) { + if ((framecnt_t) vector.len[0] > samples_to_read) { /* we're not going to fill the first chunk, so certainly do not bother with the other part. it won't be connected with the part we do fill, as in: .... => writable space ++++ => readable space - ^^^^ => 1 x disk_io_chunk_frames that would be filled + ^^^^ => 1 x disk_read_chunk_frames that would be filled |......|+++++++++++++|...............................| buf1 buf0 @@ -1166,11 +1311,13 @@ AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) len2 = vector.len[1]; to_read = min (ts, len1); - to_read = min (to_read, disk_io_chunk_frames); + to_read = min (to_read, (framecnt_t) samples_to_read); + + assert (to_read >= 0); if (to_read) { - if (read (buf1, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan, chan_n, reversed)) { + if (read (buf1, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan_n, reversed)) { ret = -1; goto out; } @@ -1183,11 +1330,12 @@ AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) if (to_read) { - /* we read all of vector.len[0], but it wasn't an entire disk_io_chunk_frames of data, - so read some or all of vector.len[1] as well. + /* we read all of vector.len[0], but it wasn't the + entire samples_to_read of data, so read some or + all of vector.len[1] as well. */ - if (read (buf2, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan, chan_n, reversed)) { + if (read (buf2, mixdown_buffer, gain_buffer, file_frame_tmp, to_read, chan_n, reversed)) { ret = -1; goto out; } @@ -1196,26 +1344,33 @@ AudioDiskstream::_do_refill (Sample* mixdown_buffer, float* gain_buffer) } if (zero_fill) { - /* do something */ + /* XXX: do something */ } } + // elapsed = g_get_monotonic_time () - before; + // cerr << "\tbandwidth = " << (byte_size_for_read / 1048576.0) / (elapsed/1000000.0) << "MB/sec\n"; + file_frame = file_frame_tmp; + assert (file_frame >= 0); - out: + ret = ((total_space - samples_to_read) > disk_read_chunk_frames); + c->front()->playback_buf->get_write_vector (&vector); + + out: return ret; } /** 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 @@ -1227,8 +1382,6 @@ AudioDiskstream::do_flush (RunContext /*context*/, bool force_flush) RingBufferNPT::rw_vector transvec; framecnt_t total; - _write_data_count = 0; - transvec.buf[0] = 0; transvec.buf[1] = 0; vector.buf[0] = 0; @@ -1241,7 +1394,7 @@ AudioDiskstream::do_flush (RunContext /*context*/, bool force_flush) total = vector.len[0] + vector.len[1]; - if (total == 0 || (total < disk_io_chunk_frames && !force_flush && was_recording)) { + if (total == 0 || (total < disk_write_chunk_frames && !force_flush && was_recording)) { goto out; } @@ -1256,11 +1409,11 @@ AudioDiskstream::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 = min (disk_io_chunk_frames, (framecnt_t) vector.len[0]); + to_write = min (disk_write_chunk_frames, (framecnt_t) vector.len[0]); // check the transition buffer when recording destructive // important that we get this after the capture buf @@ -1268,7 +1421,6 @@ AudioDiskstream::do_flush (RunContext /*context*/, bool force_flush) if (destructive()) { (*chan)->capture_transition_buf->get_read_vector(&transvec); size_t transcount = transvec.len[0] + transvec.len[1]; - bool have_start = false; size_t ti; for (ti=0; ti < transcount; ++ti) { @@ -1280,9 +1432,7 @@ AudioDiskstream::do_flush (RunContext /*context*/, bool force_flush) (*chan)->write_source->mark_capture_start (captrans.capture_val); (*chan)->curr_capture_cnt = 0; - have_start = true; - } - else if (captrans.type == CaptureEnd) { + } else if (captrans.type == CaptureEnd) { // capture end, the capture_val represents total frames in capture @@ -1316,29 +1466,29 @@ AudioDiskstream::do_flush (RunContext /*context*/, bool force_flush) } 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; + error << string_compose(_("AudioDiskstream %1: cannot write to disk"), id()) << endmsg; return -1; } (*chan)->capture_buf->increment_read_ptr (to_write); (*chan)->curr_capture_cnt += to_write; - if ((to_write == vector.len[0]) && (total > to_write) && (to_write < disk_io_chunk_frames) && !destructive()) { + if ((to_write == vector.len[0]) && (total > to_write) && (to_write < disk_write_chunk_frames) && !destructive()) { /* we wrote all of vector.len[0] but it wasn't an entire - disk_io_chunk_frames of data, so arrange for some part + 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)(disk_io_chunk_frames - to_write), (framecnt_t) vector.len[1]); + to_write = min ((framecnt_t)(disk_write_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; + error << string_compose(_("AudioDiskstream %1: cannot write to disk"), id()) << endmsg; return -1; } - _write_data_count += (*chan)->write_source->write_data_count(); - (*chan)->capture_buf->increment_read_ptr (to_write); (*chan)->curr_capture_cnt += to_write; } @@ -1364,7 +1514,7 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo uint32_t n = 0; bool mark_write_completed = false; - finish_capture (true, c); + finish_capture (c); /* butler is already stopped, but there may be work to do to flush remaining data to disk. @@ -1384,7 +1534,7 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo } /* 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; @@ -1402,7 +1552,6 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo (*chan)->write_source->mark_for_remove (); (*chan)->write_source->drop_references (); - _session.remove_source ((*chan)->write_source); (*chan)->write_source.reset (); } @@ -1424,9 +1573,6 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo if (s) { srcs.push_back (s); - if (s->unstubify ()) { - error << string_compose (_("Could not move capture file from %1"), s->path()) << endmsg; - } s->update_header (capture_info.front()->start, when, twhen); s->set_captured_for (_name.val()); s->mark_immutable (); @@ -1434,6 +1580,8 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo 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))); } } @@ -1482,9 +1630,8 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo _last_capture_sources.insert (_last_capture_sources.end(), srcs.begin(), srcs.end()); - // cerr << _name << ": there are " << capture_info.size() << " capture_info records\n"; - - _playlist->clear_changes (); + _playlist->clear_changes (); + _playlist->set_capture_insertion_in_progress (true); _playlist->freeze (); for (buffer_position = c->front()->write_source->last_capture_start_frame(), ci = capture_info.begin(); ci != capture_info.end(); ++ci) { @@ -1493,16 +1640,17 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo RegionFactory::region_name (region_name, whole_file_region_name, false); - // cerr << _name << ": based on ci of " << (*ci)->start << " for " << (*ci)->frames << " add region " << region_name << endl; + 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 rx (RegionFactory::create (srcs, plist)); region = boost::dynamic_pointer_cast (rx); } @@ -1514,21 +1662,15 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo i_am_the_modifier++; - if (_playlist->explicit_relayering()) { - /* We are in `explicit relayering' mode, so we must specify which layer this new region - should end up on. Put it at the top. - */ - region->set_layer (_playlist->top_layer() + 1); - region->set_pending_explicit_relayer (true); - } - _playlist->add_region (region, (*ci)->start, 1, non_layered()); + _playlist->set_layer (region, DBL_MAX); i_am_the_modifier--; buffer_position += (*ci)->frames; } _playlist->thaw (); + _playlist->set_capture_insertion_in_progress (false); _session.add_command (new StatefulDiffCommand (_playlist)); } @@ -1554,19 +1696,7 @@ AudioDiskstream::transport_looped (framepos_t transport_frame) // all we need to do is finish this capture, with modified capture length boost::shared_ptr c = channels.reader(); - // 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, c); + finish_capture (c); // the next region will start recording via the normal mechanism // we'll set the start position to the current transport pos @@ -1599,11 +1729,11 @@ AudioDiskstream::transport_looped (framepos_t transport_frame) } void -AudioDiskstream::finish_capture (bool /*rec_monitors_input*/, boost::shared_ptr c) +AudioDiskstream::finish_capture (boost::shared_ptr c) { was_recording = false; - first_recordable_frame = max_framepos; - last_recordable_frame = max_framepos; + first_recordable_frame = max_framepos; + last_recordable_frame = max_framepos; if (capture_captured == 0) { return; @@ -1642,7 +1772,7 @@ AudioDiskstream::finish_capture (bool /*rec_monitors_input*/, boost::shared_ptr< accessors, so that invalidation will not occur (both non-realtime). */ - // cerr << "Finish capture, add new CI, " << ci->start << '+' << ci->frames << endl; + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("Finish capture, add new CI, %1 + %2\n", ci->start, ci->frames)); capture_info.push_back (ci); capture_captured = 0; @@ -1654,7 +1784,7 @@ AudioDiskstream::finish_capture (bool /*rec_monitors_input*/, boost::shared_ptr< void AudioDiskstream::set_record_enabled (bool yn) { - if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_audio() == 0) { + if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_audio() == 0 || record_safe ()) { return; } @@ -1674,75 +1804,102 @@ AudioDiskstream::set_record_enabled (bool yn) } else { disengage_record_enable (); } + + RecordEnableChanged (); /* EMIT SIGNAL */ } } void -AudioDiskstream::engage_record_enable () +AudioDiskstream::set_record_safe (bool yn) +{ + if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_audio() == 0) { + return; + } + + /* can't rec-safe in destructive mode if transport is before start ???? + REQUIRES REVIEW */ + + if (destructive() && yn && _session.transport_frame() < _session.current_start_frame()) { + 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 +AudioDiskstream::prep_record_enable () { + if (!recordable() || !_session.record_enabling_legal() || _io->n_ports().n_audio() == 0 || record_safe ()) { // REQUIRES REVIEW "|| record_safe ()" + return false; + } + + /* can't rec-enable in destructive mode if transport is before start */ + + if (destructive() && _session.transport_frame() < _session.current_start_frame()) { + return false; + } + bool rolling = _session.transport_speed() != 0.0f; boost::shared_ptr c = channels.reader(); - g_atomic_int_set (&_record_enabled, 1); capturing_sources.clear (); if (Config->get_monitoring_model() == HardwareMonitoring) { for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - if ((*chan)->source) { - (*chan)->source->ensure_monitor_input (!(_session.config.get_auto_input() && rolling)); - } + (*chan)->source.request_input_monitoring (!(_session.config.get_auto_input() && rolling)); capturing_sources.push_back ((*chan)->write_source); - (*chan)->write_source->mark_streaming_write_started (); + Source::Lock lock((*chan)->write_source->mutex()); + (*chan)->write_source->mark_streaming_write_started (lock); } } else { for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { capturing_sources.push_back ((*chan)->write_source); - (*chan)->write_source->mark_streaming_write_started (); + Source::Lock lock((*chan)->write_source->mutex()); + (*chan)->write_source->mark_streaming_write_started (lock); } } - RecordEnableChanged (); /* EMIT SIGNAL */ + return true; } -void -AudioDiskstream::disengage_record_enable () +bool +AudioDiskstream::prep_record_disable () { - g_atomic_int_set (&_record_enabled, 0); boost::shared_ptr c = channels.reader(); if (Config->get_monitoring_model() == HardwareMonitoring) { for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - if ((*chan)->source) { - (*chan)->source->ensure_monitor_input (false); - } + (*chan)->source.request_input_monitoring (false); } } capturing_sources.clear (); - RecordEnableChanged (); /* EMIT SIGNAL */ + + return true; } XMLNode& AudioDiskstream::get_state () { - XMLNode* node = new XMLNode ("Diskstream"); + XMLNode& node (Diskstream::get_state()); char buf[64] = ""; - LocaleGuard lg (X_("POSIX")); - boost::shared_ptr c = channels.reader(); + LocaleGuard lg; - node->add_property ("flags", enum_2_string (_flags)); - - snprintf (buf, sizeof(buf), "%zd", c->size()); - node->add_property ("channels", buf); - - node->add_property ("playlist", _playlist->name()); - - snprintf (buf, sizeof(buf), "%.12g", _visible_speed); - node->add_property ("speed", buf); - - node->add_property("name", _name); - id().print (buf, sizeof (buf)); - node->add_property("id", buf); + boost::shared_ptr c = channels.reader(); + snprintf (buf, sizeof(buf), "%u", (unsigned int) c->size()); + node.add_property ("channels", buf); if (!capturing_sources.empty() && _session.get_record_enabled()) { @@ -1766,25 +1923,23 @@ AudioDiskstream::get_state () } cs_child->add_property (X_("at"), buf); - node->add_child_nocopy (*cs_child); + node.add_child_nocopy (*cs_child); } - if (_extra_xml) { - node->add_child_copy (*_extra_xml); - } - - return* node; + return node; } int -AudioDiskstream::set_state (const XMLNode& node, int /*version*/) +AudioDiskstream::set_state (const XMLNode& node, int version) { - const XMLProperty* prop; + XMLProperty const * prop; XMLNodeList nlist = node.children(); XMLNodeIterator niter; uint32_t nchans = 1; XMLNode* capture_pending_node = 0; - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg; + + /* prevent write sources from being created */ in_set_state = true; @@ -1798,26 +1953,8 @@ AudioDiskstream::set_state (const XMLNode& node, int /*version*/) } } - /* prevent write sources from being created */ - - in_set_state = true; - - if ((prop = node.property ("name")) != 0) { - _name = prop->value(); - } - - if (deprecated_io_node) { - if ((prop = deprecated_io_node->property ("id")) != 0) { - _id = prop->value (); - } - } else { - if ((prop = node.property ("id")) != 0) { - _id = prop->value (); - } - } - - if ((prop = node.property ("flags")) != 0) { - _flags = Flag (string_2_enum (prop->value(), _flags)); + if (Diskstream::set_state (node, version)) { + return -1; } if ((prop = node.property ("channels")) != 0) { @@ -1839,37 +1976,14 @@ AudioDiskstream::set_state (const XMLNode& node, int /*version*/) remove_channel (_n_channels.n_audio() - nchans); } - if ((prop = node.property ("playlist")) == 0) { - return -1; - } - - { - bool had_playlist = (_playlist != 0); - - if (find_and_use_playlist (prop->value())) { - return -1; - } - if (!had_playlist) { - _playlist->set_orig_diskstream_id (id()); - } - if (!destructive() && capture_pending_node) { - /* destructive streams have one and only one source per channel, - and so they never end up in pending capture in any useful - sense. - */ - use_pending_capture_data (*capture_pending_node); - } - - } - - if ((prop = node.property ("speed")) != 0) { - double sp = atof (prop->value().c_str()); - - if (realtime_set_speed (sp, false)) { - non_realtime_set_speed (); - } + if (!destructive() && capture_pending_node) { + /* destructive streams have one and only one source per channel, + and so they never end up in pending capture in any useful + sense. + */ + use_pending_capture_data (*capture_pending_node); } in_set_state = false; @@ -1902,13 +2016,8 @@ AudioDiskstream::use_new_write_source (uint32_t n) ChannelInfo* chan = (*c)[n]; try { - /* file starts off as a stub file, it will be converted - when we're done with a capture pass. - */ - - if ((chan->write_source = _session.create_audio_source_for_session (n_channels().n_audio(), - name(), n, destructive(), - true)) == 0) { + if ((chan->write_source = _session.create_audio_source_for_session ( + n_channels().n_audio(), write_source_name(), n, destructive())) == 0) { throw failed_constructor(); } } @@ -1922,16 +2031,8 @@ AudioDiskstream::use_new_write_source (uint32_t n) /* do not remove destructive files even if they are empty */ chan->write_source->set_allow_remove_if_empty (!destructive()); - - return 0; -} -list > -AudioDiskstream::steal_write_sources() -{ - /* not possible to steal audio write sources */ - list > ret; - return ret; + return 0; } void @@ -1954,18 +2055,18 @@ AudioDiskstream::reset_write_sources (bool mark_write_complete, bool /*force*/) if ((*chan)->write_source) { if (mark_write_complete) { - (*chan)->write_source->mark_streaming_write_completed (); + 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 (); - _session.remove_source ((*chan)->write_source); - } - - (*chan)->write_source.reset (); - } + if ((*chan)->write_source->removable()) { + (*chan)->write_source->mark_for_remove (); + (*chan)->write_source->drop_references (); + } + + (*chan)->write_source.reset (); + } use_new_write_source (n); @@ -1993,23 +2094,6 @@ AudioDiskstream::reset_write_sources (bool mark_write_complete, bool /*force*/) } } -int -AudioDiskstream::rename_write_sources () -{ - ChannelList::iterator chan; - boost::shared_ptr c = channels.reader(); - uint32_t n; - - for (chan = c->begin(), n = 0; chan != c->end(); ++chan, ++n) { - if ((*chan)->write_source != 0) { - (*chan)->write_source->set_source_name (_name.val(), destructive()); - /* XXX what to do if one of them fails ? */ - } - } - - return 0; -} - void AudioDiskstream::set_block_size (pframes_t /*nframes*/) { @@ -2034,19 +2118,21 @@ AudioDiskstream::allocate_temporary_buffers () when slaving to MTC, Timecode etc. */ - double const sp = max (fabsf (_actual_speed), 1.2f); - framecnt_t required_wrap_size = (framecnt_t) floor (_session.get_block_size() * sp) + 1; + double const sp = max (fabs (_actual_speed), 1.2); + framecnt_t required_wrap_size = (framecnt_t) ceil (_session.get_block_size() * sp) + 2; if (required_wrap_size > wrap_buffer_size) { boost::shared_ptr c = channels.reader(); for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - if ((*chan)->playback_wrap_buffer) + if ((*chan)->playback_wrap_buffer) { delete [] (*chan)->playback_wrap_buffer; + } (*chan)->playback_wrap_buffer = new Sample[required_wrap_size]; - if ((*chan)->capture_wrap_buffer) + if ((*chan)->capture_wrap_buffer) { delete [] (*chan)->capture_wrap_buffer; + } (*chan)->capture_wrap_buffer = new Sample[required_wrap_size]; } @@ -2055,15 +2141,12 @@ AudioDiskstream::allocate_temporary_buffers () } void -AudioDiskstream::monitor_input (bool yn) +AudioDiskstream::request_input_monitoring (bool yn) { boost::shared_ptr c = channels.reader(); for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - - if ((*chan)->source) { - (*chan)->source->ensure_monitor_input (yn); - } + (*chan)->source.request_input_monitoring (yn); } } @@ -2072,6 +2155,10 @@ AudioDiskstream::set_align_style_from_io () { bool have_physical = false; + if (_alignment_choice != Automatic) { + return; + } + if (_io == 0) { return; } @@ -2081,7 +2168,7 @@ AudioDiskstream::set_align_style_from_io () boost::shared_ptr c = channels.reader(); for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - if ((*chan)->source && (*chan)->source->flags() & JackPortIsPhysical) { + if ((*chan)->source.is_physical ()) { have_physical = true; break; } @@ -2098,10 +2185,13 @@ int AudioDiskstream::add_channel_to (boost::shared_ptr c, uint32_t how_many) { while (how_many--) { - c->push_back (new ChannelInfo(_session.butler()->audio_diskstream_playback_buffer_size(), - _session.butler()->audio_diskstream_capture_buffer_size(), - speed_buffer_size, wrap_buffer_size)); - interpolation.add_channel_to (_session.butler()->audio_diskstream_playback_buffer_size(), speed_buffer_size); + c->push_back (new ChannelInfo( + _session.butler()->audio_diskstream_playback_buffer_size(), + _session.butler()->audio_diskstream_capture_buffer_size(), + speed_buffer_size, wrap_buffer_size)); + interpolation.add_channel_to ( + _session.butler()->audio_diskstream_playback_buffer_size(), + speed_buffer_size); } _n_channels.set(DataType::AUDIO, c->size()); @@ -2147,11 +2237,11 @@ AudioDiskstream::playback_buffer_load () const boost::shared_ptr c = channels.reader(); if (c->empty ()) { - return 0; + return 1.0; } return (float) ((double) c->front()->playback_buf->read_space()/ - (double) c->front()->playback_buf->bufsize()); + (double) c->front()->playback_buf->bufsize()); } float @@ -2160,9 +2250,9 @@ AudioDiskstream::capture_buffer_load () const boost::shared_ptr c = channels.reader(); if (c->empty ()) { - return 0; + return 1.0; } - + return (float) ((double) c->front()->capture_buf->write_space()/ (double) c->front()->capture_buf->bufsize()); } @@ -2170,7 +2260,7 @@ AudioDiskstream::capture_buffer_load () const int AudioDiskstream::use_pending_capture_data (XMLNode& node) { - const XMLProperty* prop; + XMLProperty const * prop; XMLNodeList nlist = node.children(); XMLNodeIterator niter; boost::shared_ptr fs; @@ -2194,15 +2284,21 @@ AudioDiskstream::use_pending_capture_data (XMLNode& node) } // This protects sessions from errant CapturingSources in stored sessions - struct stat sbuf; - if (stat (prop->value().c_str(), &sbuf)) { + GStatBuf sbuf; + if (g_stat (prop->value().c_str(), &sbuf)) { continue; } + /* XXX as of June 2014, we always record to mono + files. Since this Source is being created as part of + crash recovery, we know that we need the first + channel (the final argument to the SourceFactory + call below). If we ever support non-mono files for + capture, this will need rethinking. + */ + try { - fs = boost::dynamic_pointer_cast ( - SourceFactory::createWritable (DataType::AUDIO, _session, - prop->value(), string(), false, _session.frame_rate())); + fs = boost::dynamic_pointer_cast (SourceFactory::createForRecovery (DataType::AUDIO, _session, prop->value(), 0)); } catch (failed_constructor& err) { @@ -2233,21 +2329,31 @@ AudioDiskstream::use_pending_capture_data (XMLNode& node) return -1; } - boost::shared_ptr region; - try { - + + boost::shared_ptr wf_region; + boost::shared_ptr region; + + /* First create the whole file region */ + PropertyList plist; - + plist.add (Properties::start, 0); plist.add (Properties::length, first_fs->length (first_fs->timeline_position())); plist.add (Properties::name, region_name_from_path (first_fs->name(), true)); + wf_region = boost::dynamic_pointer_cast (RegionFactory::create (pending_sources, plist)); + + wf_region->set_automatic (true); + wf_region->set_whole_file (true); + wf_region->special_set_position (position); + + /* Now create a region that isn't the whole file for adding to + * the playlist */ + region = boost::dynamic_pointer_cast (RegionFactory::create (pending_sources, plist)); - region->set_automatic (true); - region->set_whole_file (true); - region->special_set_position (0); + _playlist->add_region (region, position); } catch (failed_constructor& err) { @@ -2258,7 +2364,6 @@ AudioDiskstream::use_pending_capture_data (XMLNode& node) return -1; } - _playlist->add_region (region, position); return 0; } @@ -2305,11 +2410,22 @@ AudioDiskstream::set_destructive (bool yn) bool AudioDiskstream::can_become_destructive (bool& requires_bounce) const { + if (Profile->get_trx()) { + return false; + } + if (!_playlist) { requires_bounce = false; return false; } + /* if no regions are present: easy */ + + if (_playlist->n_regions() == 0) { + requires_bounce = false; + return true; + } + /* is there only one region ? */ if (_playlist->n_regions() != 1) { @@ -2317,7 +2433,14 @@ AudioDiskstream::can_become_destructive (bool& requires_bounce) const return false; } - boost::shared_ptr first = _playlist->find_next_region (_session.current_start_frame(), Start, 1); + boost::shared_ptr first; + { + const RegionList& rl (_playlist->region_list_property().rlist()); + assert((rl.size() == 1)); + first = rl.front(); + + } + if (!first) { requires_bounce = false; return true; @@ -2326,12 +2449,24 @@ AudioDiskstream::can_become_destructive (bool& requires_bounce) const /* do the source(s) for the region cover the session start position ? */ if (first->position() != _session.current_start_frame()) { + // what is the idea here? why start() ?? if (first->start() > _session.current_start_frame()) { requires_bounce = true; return false; } } + /* currently RouteTimeAxisView::set_track_mode does not + * implement bounce. Existing regions cannot be converted. + * + * so let's make sure this region is already set up + * as tape-track (spanning the complete range) + */ + if (first->length() != max_framepos - first->position()) { + requires_bounce = true; + return false; + } + /* is the source used by only 1 playlist ? */ boost::shared_ptr afirst = boost::dynamic_pointer_cast (first); @@ -2347,30 +2482,48 @@ AudioDiskstream::can_become_destructive (bool& requires_bounce) const return true; } -void +void AudioDiskstream::adjust_playback_buffering () { boost::shared_ptr c = channels.reader(); for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->resize_playback (_session.butler()->audio_diskstream_playback_buffer_size()); - } + (*chan)->resize_playback (_session.butler()->audio_diskstream_playback_buffer_size()); + } } -void +void AudioDiskstream::adjust_capture_buffering () { boost::shared_ptr c = channels.reader(); for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->resize_capture (_session.butler()->audio_diskstream_capture_buffer_size()); - } + (*chan)->resize_capture (_session.butler()->audio_diskstream_capture_buffer_size()); + } +} + +bool +AudioDiskstream::ChannelSource::is_physical () const +{ + if (name.empty()) { + return false; + } + + return AudioEngine::instance()->port_is_physical (name); +} + +void +AudioDiskstream::ChannelSource::request_input_monitoring (bool yn) const +{ + if (name.empty()) { + return; + } + + return AudioEngine::instance()->request_input_monitoring (name, yn); } AudioDiskstream::ChannelInfo::ChannelInfo (framecnt_t playback_bufsize, framecnt_t capture_bufsize, framecnt_t speed_size, framecnt_t wrap_size) { - peak_power = 0.0f; - source = 0; current_capture_buffer = 0; current_playback_buffer = 0; curr_capture_cnt = 0; @@ -2397,7 +2550,7 @@ AudioDiskstream::ChannelInfo::ChannelInfo (framecnt_t playback_bufsize, framecnt void AudioDiskstream::ChannelInfo::resize_playback (framecnt_t playback_bufsize) { - delete playback_buf; + delete playback_buf; playback_buf = new RingBufferNPT (playback_bufsize); memset (playback_buf->buffer(), 0, sizeof (Sample) * playback_buf->bufsize()); } @@ -2405,7 +2558,7 @@ AudioDiskstream::ChannelInfo::resize_playback (framecnt_t playback_bufsize) void AudioDiskstream::ChannelInfo::resize_capture (framecnt_t capture_bufsize) { - delete capture_buf; + delete capture_buf; capture_buf = new RingBufferNPT (capture_bufsize); memset (capture_buf->buffer(), 0, sizeof (Sample) * capture_buf->bufsize()); @@ -2413,7 +2566,25 @@ AudioDiskstream::ChannelInfo::resize_capture (framecnt_t capture_bufsize) AudioDiskstream::ChannelInfo::~ChannelInfo () { - write_source.reset (); + if (write_source) { + if (write_source->removable()) { + /* this is a "stub" write source which exists in the + Session source list, but is removable. We must emit + a drop references call because it should not + continue to exist. If we do not do this, then the + Session retains a reference to it, it is not + deleted, and later attempts to create a new source + file will use wierd naming because it already + exists. + + XXX longer term TO-DO: do not add to session source + list until we write to the source. + */ + write_source->drop_references (); + } + } + + write_source.reset (); delete [] speed_buffer; speed_buffer = 0; @@ -2434,3 +2605,45 @@ AudioDiskstream::ChannelInfo::~ChannelInfo () capture_transition_buf = 0; } + +bool +AudioDiskstream::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 */ + + boost::shared_ptr c = channels.reader(); + ChannelList::iterator i; + int n = 0; + + for (n = 0, i = c->begin(); i != c->end(); ++i, ++n) { + use_new_write_source (n); + } + + return true; +} + +bool +AudioDiskstream::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; + } + boost::shared_ptr c = channels.reader(); + ChannelList::iterator i; + int n = 0; + + for (n = 0, i = c->begin(); i != c->end(); ++i, ++n) { + use_new_write_source (n); + } + return true; +}