X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fdisk_writer.cc;h=fcba9d049b80e694a8d8e6c4b1d078e061d54d21;hb=bdf3067a59672d67913d66d6a9b2449a8211d910;hp=2b38e0a2393d825f3b01b61e4b3c1f38ee2c2a25;hpb=690c02c31a000439cf8b80acd6e96e68974c1b93;p=ardour.git diff --git a/libs/ardour/disk_writer.cc b/libs/ardour/disk_writer.cc index 2b38e0a239..fcba9d049b 100644 --- a/libs/ardour/disk_writer.cc +++ b/libs/ardour/disk_writer.cc @@ -45,7 +45,9 @@ PBD::Signal0 DiskWriter::Overrun; DiskWriter::DiskWriter (Session& s, string const & str, DiskIOProcessor::Flag f) : DiskIOProcessor (s, str, f) - , capture_start_frame (0) + , _record_enabled (0) + , _record_safe (0) + , capture_start_frame (0) , capture_captured (0) , was_recording (false) , adjust_capture_position (0) @@ -62,6 +64,11 @@ DiskWriter::DiskWriter (Session& s, string const & str, DiskIOProcessor::Flag f) DiskIOProcessor::init (); } +DiskWriter::~DiskWriter () +{ + DEBUG_TRACE (DEBUG::Destruction, string_compose ("DiskWriter %1 @ %2 deleted\n", _name, this)); +} + framecnt_t DiskWriter::default_chunk_frames () { @@ -284,7 +291,8 @@ DiskWriter::get_captured_frames (uint32_t n) const void DiskWriter::set_input_latency (framecnt_t l) { - _input_latency = l; + Processor::set_input_latency (l); + set_capture_offset (); } void @@ -301,7 +309,7 @@ DiskWriter::set_capture_offset () break; } - DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: using IO latency, capture offset set to %2 with style = %3\n", name(), _capture_offset, enum_2_string (_alignment_style))); + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: using input latency %4, capture offset set to %2 with style = %3\n", name(), _capture_offset, enum_2_string (_alignment_style), _input_latency)); } @@ -319,62 +327,6 @@ DiskWriter::set_align_style (AlignStyle a, bool force) } } -void -DiskWriter::set_align_style_from_io () -{ - bool have_physical = false; - - if (_alignment_choice != Automatic) { - return; - } - - if (!_route) { - return; - } - - boost::shared_ptr input = _route->input (); - - if (input) { - uint32_t n = 0; - vector connections; - boost::shared_ptr c = channels.reader(); - - for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan, ++n) { - - if ((input->nth (n).get()) && (input->nth (n)->get_connections (connections) == 0)) { - if (AudioEngine::instance()->port_is_physical (connections[0])) { - have_physical = true; - break; - } - } - - connections.clear (); - } - } - -#ifdef MIXBUS - // compensate for latency when bouncing from master or mixbus. - // we need to use "ExistingMaterial" to pick up the master bus' latency - // see also Route::direct_feeds_according_to_reality - IOVector ios; - ios.push_back (_io); - if (_session.master_out() && ios.fed_by (_session.master_out()->output())) { - have_physical = true; - } - for (uint32_t n = 0; n < NUM_MIXBUSES && !have_physical; ++n) { - if (_session.get_mixbus (n) && ios.fed_by (_session.get_mixbus(n)->output())) { - have_physical = true; - } - } -#endif - - if (have_physical) { - set_align_style (ExistingMaterial); - } else { - set_align_style (CaptureTime); - } -} - void DiskWriter::set_align_choice (AlignChoice a, bool force) { @@ -386,15 +338,15 @@ DiskWriter::set_align_choice (AlignChoice a, bool force) _alignment_choice = a; switch (_alignment_choice) { - case Automatic: - set_align_style_from_io (); - break; - case UseExistingMaterial: - set_align_style (ExistingMaterial); - break; - case UseCaptureTime: - set_align_style (CaptureTime); - break; + case UseExistingMaterial: + set_align_style (ExistingMaterial); + break; + case UseCaptureTime: + set_align_style (CaptureTime); + break; + default: + error << string_compose (_("programming error: %1"), "DiskWriter: asked to use illegal alignment style") << endmsg; + break; } } } @@ -403,7 +355,7 @@ XMLNode& DiskWriter::state (bool full) { XMLNode& node (DiskIOProcessor::state (full)); - node.set_property(X_("type"), X_("diskwriter")); + node.set_property (X_("type"), X_("diskwriter")); node.set_property (X_("capture-alignment"), enum_2_string (_alignment_choice)); node.set_property (X_("record-safe"), (_record_safe ? X_("yes" : "no"))); return node; @@ -412,23 +364,24 @@ DiskWriter::state (bool full) int DiskWriter::set_state (const XMLNode& node, int version) { - XMLProperty const * prop; - if (DiskIOProcessor::set_state (node, version)) { return -1; } -#if 0 // XXX DISK - if (!node.property (X_("capture-alignment"))) != 0) { - set_align_choice (AlignChoice (string_2_enum (prop->value(), _alignment_choice)), true); + + AlignChoice ac; + + if (node.get_property (X_("capture-alignment"), ac)) { + set_align_choice (ac, true); } else { set_align_choice (Automatic, true); } -#endif if (!node.get_property (X_("record-safe"), _record_safe)) { _record_safe = false; } + reset_write_sources (false, true); + return 0; } @@ -488,6 +441,19 @@ DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, bool re = record_enabled (); bool can_record = _session.actively_recording (); + if (_active) { + if (!_pending_active) { + _active = false; + return; + } + } else { + if (_pending_active) { + _active = true; + } else { + return; + } + } + _need_butler = false; check_record_status (start_frame, can_record); @@ -571,7 +537,7 @@ DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, const size_t n_buffers = bufs.count().n_audio(); - for (n = 0; chan != c->end(); ++chan, ++n) { + for (chan = c->begin(), n = 0; chan != c->end(); ++chan, ++n) { ChannelInfo* chaninfo (*chan); AudioBuffer& buf (bufs.get_audio (n%n_buffers)); @@ -701,7 +667,7 @@ DiskWriter::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, _need_butler = true; } - DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 writer run, needs butler = %2\n", name(), _need_butler)); + // DEBUG_TRACE (DEBUG::Butler, string_compose ("%1 writer run, needs butler = %2\n", name(), _need_butler)); } void @@ -739,6 +705,8 @@ DiskWriter::finish_capture (boost::shared_ptr c) ci->start = capture_start_frame; ci->frames = capture_captured; + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("Finish capture, add new CI, %1 + %2\n", ci->start, ci->frames)); + /* XXX theoretical race condition here. Need atomic exchange ? However, the circumstances when this is called right now (either on record-disable or transport_stopped) @@ -748,8 +716,6 @@ DiskWriter::finish_capture (boost::shared_ptr c) accessors, so that invalidation will not occur (both non-realtime). */ - 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; @@ -1025,6 +991,48 @@ DiskWriter::do_flush (RunContext ctxt, bool force_flush) /* MIDI*/ + if (_midi_write_source) { + + const framecnt_t total = g_atomic_int_get(const_cast (&_frames_pending_write)); + + if (total == 0 || + _midi_buf->read_space() == 0 || + (!force_flush && (total < _chunk_frames) && was_recording)) { + goto out; + } + + /* if there are 2+ chunks of disk i/o possible for + this track), let the caller know so that it can arrange + 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; + } + + if (force_flush) { + /* push out everything we have, right now */ + to_write = UINT32_MAX; + } else { + to_write = _chunk_frames; + } + + if (record_enabled() && ((total > _chunk_frames) || force_flush)) { + Source::Lock lm(_midi_write_source->mutex()); + if (_midi_write_source->midi_write (lm, *_midi_buf, get_capture_start_frame (0), to_write) != to_write) { + error << string_compose(_("MidiDiskstream %1: cannot write to disk"), id()) << endmsg; + return -1; + } + g_atomic_int_add(const_cast (&_frames_pending_write), -to_write); + } + } + out: return ret; @@ -1082,18 +1090,20 @@ DiskWriter::reset_write_sources (bool mark_write_complete, bool /*force*/) Source::Lock lm(_midi_write_source->mutex()); _midi_write_source->mark_streaming_write_completed (lm); } + } + if (_playlists[DataType::MIDI]) { use_new_write_source (DataType::MIDI); + } - if (destructive() && !c->empty ()) { + if (destructive() && !c->empty ()) { - /* we now have all our write sources set up, so create the - playlist's single region. - */ + /* we now have all our write sources set up, so create the + playlist's single region. + */ - if (_playlists[DataType::MIDI]->empty()) { - setup_destructive_playlist (); - } + if (_playlists[DataType::MIDI]->empty()) { + setup_destructive_playlist (); } } } @@ -1158,13 +1168,11 @@ DiskWriter::use_new_write_source (DataType dt, uint32_t n) 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 region; framecnt_t total_capture; - SourceList srcs; - SourceList::iterator src; + SourceList audio_srcs; + SourceList midi_srcs; ChannelList::iterator chan; vector::iterator ci; boost::shared_ptr c = channels.reader(); @@ -1173,7 +1181,6 @@ DiskWriter::transport_stopped_wallclock (struct tm& when, time_t twhen, bool abo finish_capture (c); - boost::shared_ptr pl = boost::dynamic_pointer_cast (_playlists[DataType::AUDIO]); /* butler is already stopped, but there may be work to do to flush remaining data to disk. @@ -1217,6 +1224,12 @@ DiskWriter::transport_stopped_wallclock (struct tm& when, time_t twhen, bool abo /* new source set up in "out" below */ } + if (_midi_write_source) { + _midi_write_source->mark_for_remove (); + _midi_write_source->drop_references (); + _midi_write_source.reset(); + } + goto out; } @@ -1228,117 +1241,76 @@ DiskWriter::transport_stopped_wallclock (struct tm& when, time_t twhen, bool abo for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) { - boost::shared_ptr s = (*chan)->write_source; + boost::shared_ptr as = (*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 (as) { + audio_srcs.push_back (as); + as->update_header (capture_info.front()->start, when, twhen); + as->set_captured_for (_name.val()); + as->mark_immutable (); if (Config->get_auto_analyse_audio()) { - Analyser::queue_source_for_analysis (s, true); + Analyser::queue_source_for_analysis (as, true); } - DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("newly captured source %1 length %2\n", s->path(), s->length (0))); + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("newly captured source %1 length %2\n", as->path(), as->length (0))); } - } - if (!pl) { - goto midi; + if (_midi_write_source) { + midi_srcs.push_back (_midi_write_source); + } } - /* destructive tracks have a single, never changing region */ - if (destructive()) { + /* MIDI */ - /* 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 rx (RegionFactory::create (srcs, plist)); - rx->set_automatic (true); - rx->set_whole_file (true); - - region = boost::dynamic_pointer_cast (rx); - region->special_set_position (capture_info.front()->start); - } + if (_midi_write_source) { + if (_midi_write_source->length (capture_info.front()->start) == 0) { + /* No data was recorded, so this capture will + effectively be aborted; do the same as we + do for an explicit abort. + */ + if (_midi_write_source) { + _midi_write_source->mark_for_remove (); + _midi_write_source->drop_references (); + _midi_write_source.reset(); + } - catch (failed_constructor& err) { - error << string_compose(_("%1: could not create region for complete audio file"), _name) << endmsg; - /* XXX what now? */ + goto out; } - _last_capture_sources.insert (_last_capture_sources.end(), srcs.begin(), srcs.end()); + /* phew, we have data */ - pl->clear_changes (); - pl->set_capture_insertion_in_progress (true); - pl->freeze (); + Source::Lock source_lock(_midi_write_source->mutex()); - 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) { + /* figure out the name for this take */ - string region_name; + midi_srcs.push_back (_midi_write_source); - RegionFactory::region_name (region_name, whole_file_region_name, false); + _midi_write_source->set_timeline_position (capture_info.front()->start); + _midi_write_source->set_captured_for (_name); - 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)); + /* set length in beats to entire capture length */ - try { + BeatsFramesConverter converter (_session.tempo_map(), capture_info.front()->start); + const Evoral::Beats total_capture_beats = converter.from (total_capture); + _midi_write_source->set_length_beats (total_capture_beats); - 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); - 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? */ - } + /* flush to disk: this step differs from the audio path, + where all the data is already on disk. + */ - i_am_the_modifier++; + _midi_write_source->mark_midi_streaming_write_completed (source_lock, Evoral::Sequence::ResolveStuckNotes, total_capture_beats); + } - pl->add_region (region, (*ci)->start + preroll_off, 1, non_layered()); - pl->set_layer (region, DBL_MAX); - i_am_the_modifier--; + _last_capture_sources.insert (_last_capture_sources.end(), audio_srcs.begin(), audio_srcs.end()); + _last_capture_sources.insert (_last_capture_sources.end(), midi_srcs.begin(), midi_srcs.end()); - buffer_position += (*ci)->frames; - } - pl->thaw (); - pl->set_capture_insertion_in_progress (false); - _session.add_command (new StatefulDiffCommand (pl)); + if (_route) { + _route->use_captured_sources (audio_srcs, capture_info); + _route->use_captured_sources (midi_srcs, capture_info); } mark_write_completed = true; @@ -1354,215 +1326,8 @@ DiskWriter::transport_stopped_wallclock (struct tm& when, time_t twhen, bool abo 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 region; - MidiRegion::SourceList srcs; - MidiRegion::SourceList::iterator src; - vector::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::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 rx (RegionFactory::create (srcs, plist)); - - region = boost::dynamic_pointer_cast (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 rx (RegionFactory::create (srcs, plist)); - region = boost::dynamic_pointer_cast (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) { @@ -1710,3 +1475,57 @@ DiskWriter::realtime_handle_transport_stopped () { realtime_speed_change (); } + +bool +DiskWriter::set_name (string const & str) +{ + string my_name = X_("writer:"); + my_name += str; + + if (_name != my_name) { + SessionObject::set_name (my_name); + } + + return true; +} + +std::string +DiskWriter::steal_write_source_name () +{ + if (_playlists[DataType::MIDI]) { + string our_old_name = _midi_write_source->name(); + + /* this will bump the name of the current write source to the next one + * (e.g. "MIDI 1-1" gets renamed to "MIDI 1-2"), thus leaving the + * current write source name (e.g. "MIDI 1-1" available). See the + * comments in Session::create_midi_source_by_stealing_name() about why + * we do this. + */ + + try { + string new_path = _session.new_midi_source_path (name()); + + if (_midi_write_source->rename (new_path)) { + return string(); + } + } catch (...) { + return string (); + } + + return our_old_name; + } + + return std::string(); +} + +bool +DiskWriter::configure_io (ChanCount in, ChanCount out) +{ + if (!DiskIOProcessor::configure_io (in, out)) { + return false; + } + + reset_write_sources (false, true); + + return true; +}