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)
DiskIOProcessor::init ();
}
+DiskWriter::~DiskWriter ()
+{
+ DEBUG_TRACE (DEBUG::Destruction, string_compose ("DiskWriter %1 @ %2 deleted\n", _name, this));
+
+ boost::shared_ptr<ChannelList> c = channels.reader();
+
+ for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) {
+ (*chan)->write_source.reset ();
+ }
+}
+
framecnt_t
DiskWriter::default_chunk_frames ()
{
void
DiskWriter::set_input_latency (framecnt_t l)
{
- _input_latency = l;
+ Processor::set_input_latency (l);
+ set_capture_offset ();
}
void
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));
}
}
}
-void
-DiskWriter::set_align_style_from_io ()
-{
- bool have_physical = false;
-
- if (_alignment_choice != Automatic) {
- return;
- }
-
- if (!_route) {
- return;
- }
-
- boost::shared_ptr<IO> input = _route->input ();
-
- if (input) {
- uint32_t n = 0;
- vector<string> connections;
- boost::shared_ptr<ChannelList> 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)
{
_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;
}
}
}
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;
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;
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);
_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
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)
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;
}
playback_sample = frame;
- file_frame = frame;
return 0;
}
/* MIDI*/
+ if (_midi_write_source) {
+
+ const framecnt_t total = g_atomic_int_get(const_cast<gint*> (&_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<gint*> (&_frames_pending_write), -to_write);
+ }
+ }
+
out:
return ret;
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 ();
}
}
}
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;
+ SourceList audio_srcs;
+ SourceList midi_srcs;
ChannelList::iterator chan;
vector<CaptureInfo*>::iterator ci;
boost::shared_ptr<ChannelList> c = channels.reader();
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.
/* 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;
}
for (n = 0, chan = c->begin(); chan != c->end(); ++chan, ++n) {
- boost::shared_ptr<AudioFileSource> s = (*chan)->write_source;
+ boost::shared_ptr<AudioFileSource> 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()) {
-
- /* 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);
+ /* MIDI */
- region = boost::dynamic_pointer_cast<AudioRegion> (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());
-
- 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;
+ /* phew, we have data */
- RegionFactory::region_name (region_name, whole_file_region_name, false);
+ Source::Lock source_lock(_midi_write_source->mutex());
- 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));
+ /* figure out the name for this take */
- try {
+ midi_srcs.push_back (_midi_write_source);
- PropertyList plist;
+ _midi_write_source->set_timeline_position (capture_info.front()->start);
+ _midi_write_source->set_captured_for (_name);
- plist.add (Properties::start, buffer_position);
- plist.add (Properties::length, (*ci)->frames);
- plist.add (Properties::name, region_name);
+ /* set length in beats to entire capture length */
- 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);
- }
- }
+ 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);
- 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<Evoral::Beats>::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;
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)
{
{
realtime_speed_change ();
}
+
+bool
+DiskWriter::set_name (string const & str)
+{
+ string my_name = X_("recorder:");
+ 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;
+}