X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Ftrack.cc;h=6439f8c0aacd9bffde44a7c8ee9fdba0265ed16b;hb=4bfc5ddf71728c1f14a3fbff5dab4986fbabe15c;hp=7d90709b6ff01c493202eaa18e5b3003dbacf182;hpb=f6ac5cadeaa003b38f4695cedc3ff643ef9031c1;p=ardour.git diff --git a/libs/ardour/track.cc b/libs/ardour/track.cc index 7d90709b6f..6439f8c0aa 100644 --- a/libs/ardour/track.cc +++ b/libs/ardour/track.cc @@ -18,31 +18,46 @@ #include "pbd/error.h" #include "ardour/amp.h" +#include "ardour/audioengine.h" +#include "ardour/audiofilesource.h" +#include "ardour/audioregion.h" #include "ardour/debug.h" #include "ardour/delivery.h" -#include "ardour/diskstream.h" +#include "ardour/disk_reader.h" +#include "ardour/disk_writer.h" +#include "ardour/event_type_map.h" #include "ardour/io_processor.h" #include "ardour/meter.h" +#include "ardour/midi_region.h" +#include "ardour/monitor_control.h" #include "ardour/playlist.h" +#include "ardour/playlist_factory.h" #include "ardour/port.h" #include "ardour/processor.h" +#include "ardour/profile.h" +#include "ardour/region_factory.h" +#include "ardour/record_enable_control.h" +#include "ardour/record_safe_control.h" #include "ardour/route_group_specialized.h" #include "ardour/session.h" #include "ardour/session_playlists.h" +#include "ardour/smf_source.h" #include "ardour/track.h" +#include "ardour/types_convert.h" #include "ardour/utils.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace std; using namespace ARDOUR; using namespace PBD; -Track::Track (Session& sess, string name, Route::Flag flag, TrackMode mode, DataType default_type) +Track::Track (Session& sess, string name, PresentationInfo::Flag flag, TrackMode mode, DataType default_type) : Route (sess, name, flag, default_type) , _saved_meter_point (_meter_point) + , _disk_io_point (DiskIOPreFader) , _mode (mode) - , _monitoring (MonitorAuto) + , _alignment_choice (Automatic) { _freeze_record.state = NoFreeze; _declickable = true; @@ -51,6 +66,16 @@ Track::Track (Session& sess, string name, Route::Flag flag, TrackMode mode, Data Track::~Track () { DEBUG_TRACE (DEBUG::Destruction, string_compose ("track %1 destructor\n", _name)); + + if (_disk_reader) { + _disk_reader->set_route (boost::shared_ptr()); + _disk_reader.reset (); + } + + if (_disk_writer) { + _disk_writer->set_route (boost::shared_ptr()); + _disk_writer.reset (); + } } int @@ -60,28 +85,63 @@ Track::init () return -1; } - boost::shared_ptr rp (shared_from_this()); + use_new_playlist (); + + /* disk writer and reader processors will be added when Route calls + * add_processors_oh_children_of_mine (). + */ + + boost::shared_ptr rp (boost::dynamic_pointer_cast (shared_from_this())); boost::shared_ptr rt = boost::dynamic_pointer_cast (rp); - _rec_enable_control = boost::shared_ptr (new RecEnableControl(rt)); - _rec_enable_control->set_flags (Controllable::Toggle); - /* don't add rec_enable_control to controls because we don't want it to - * appear as an automatable parameter - */ + _record_enable_control.reset (new RecordEnableControl (_session, EventTypeMap::instance().to_symbol (RecEnableAutomation), *this)); + add_control (_record_enable_control); + + _record_safe_control.reset (new RecordSafeControl (_session, EventTypeMap::instance().to_symbol (RecSafeAutomation), *this)); + add_control (_record_safe_control); + + _monitoring_control.reset (new MonitorControl (_session, EventTypeMap::instance().to_symbol (MonitoringAutomation), *this)); + add_control (_monitoring_control); + + _session.config.ParameterChanged.connect_same_thread (*this, boost::bind (&Track::parameter_changed, this, _1)); + + _monitoring_control->Changed.connect_same_thread (*this, boost::bind (&Track::monitoring_changed, this, _1, _2)); + _record_safe_control->Changed.connect_same_thread (*this, boost::bind (&Track::record_safe_changed, this, _1, _2)); + _record_enable_control->Changed.connect_same_thread (*this, boost::bind (&Track::record_enable_changed, this, _1, _2)); + + _input->changed.connect_same_thread (*this, boost::bind (&Track::input_changed, this)); return 0; } void -Track::use_new_diskstream () +Track::add_processors_oh_children_of_mine () { - boost::shared_ptr ds = create_diskstream (); + cerr << name() << " ::apocom(), create DW + DR\n"; + + DiskIOProcessor::Flag dflags = DiskIOProcessor::Recordable; - ds->do_refill_with_alloc (); - ds->set_block_size (_session.get_block_size ()); - ds->playlist()->set_orig_track_id (id()); + if (_mode == Destructive && !Profile->get_trx()) { + dflags = DiskIOProcessor::Flag (dflags | DiskIOProcessor::Destructive); + } else if (_mode == NonLayered){ + dflags = DiskIOProcessor::Flag(dflags | DiskIOProcessor::NonLayered); + } - set_diskstream (ds); + _disk_reader.reset (new DiskReader (_session, name(), dflags)); + _disk_reader->set_block_size (_session.get_block_size ()); + _disk_reader->set_route (boost::dynamic_pointer_cast (shared_from_this())); + + _disk_writer.reset (new DiskWriter (_session, name(), dflags)); + _disk_writer->set_block_size (_session.get_block_size ()); + _disk_writer->set_route (boost::dynamic_pointer_cast (shared_from_this())); +} + +void +Track::input_changed () +{ + if (_disk_writer && _alignment_choice == Automatic) { + set_align_choice_from_io (); + } } XMLNode& @@ -94,13 +154,25 @@ XMLNode& Track::state (bool full) { XMLNode& root (Route::state (full)); - root.add_property (X_("monitoring"), enum_2_string (_monitoring)); - root.add_property (X_("saved-meter-point"), enum_2_string (_saved_meter_point)); - root.add_child_nocopy (_rec_enable_control->get_state()); - root.add_child_nocopy (_diskstream->get_state ()); - + + if (_playlists[DataType::AUDIO]) { + root.set_property (X_("audio-playlist"), _playlists[DataType::AUDIO]->id().to_s()); + } + + if (_playlists[DataType::MIDI]) { + root.set_property (X_("midi-playlist"), _playlists[DataType::MIDI]->id().to_s()); + } + + root.add_child_nocopy (_monitoring_control->get_state ()); + root.add_child_nocopy (_record_safe_control->get_state ()); + root.add_child_nocopy (_record_enable_control->get_state ()); + + root.set_property (X_("saved-meter-point"), _saved_meter_point); + root.set_property (X_("disk-io-point"), _disk_io_point); + root.set_property (X_("alignment-choice"), _alignment_choice); + return root; -} +} int Track::set_state (const XMLNode& node, int version) @@ -111,46 +183,62 @@ Track::set_state (const XMLNode& node, int version) XMLNode* child; - if (version >= 3000) { + if (version >= 3000 && version < 4000) { if ((child = find_named_node (node, X_("Diskstream"))) != 0) { - boost::shared_ptr ds = diskstream_factory (*child); - ds->do_refill_with_alloc (); - set_diskstream (ds); + /* XXX if we remember anything from stored DiskStream + state (older Ardour versions) that is needed by a + DiskReader or DiskWriter, we should cook up a new + XMLNode here, populate it with that information + (child nodes, properties, etc.) and then call + ::set_state() on the writer/reader. + + But at present (June 2017), there's no such state. + */ } } - if (_diskstream) { - _diskstream->playlist()->set_orig_track_id (id()); + std::string playlist_id; + + if (node.get_property (X_("audio-playlist"), playlist_id)) { + find_and_use_playlist (DataType::AUDIO, PBD::ID (playlist_id)); } - /* set rec-enable control *AFTER* setting up diskstream, because it may - want to operate on the diskstream as it sets its own state - */ + if (node.get_property (X_("midi-playlist"), playlist_id)) { + find_and_use_playlist (DataType::MIDI, PBD::ID (playlist_id)); + } XMLNodeList nlist = node.children(); for (XMLNodeConstIterator niter = nlist.begin(); niter != nlist.end(); ++niter) { child = *niter; - XMLProperty* prop; - if (child->name() == Controllable::xml_node_name && (prop = child->property ("name")) != 0) { - if (prop->value() == X_("recenable")) { - _rec_enable_control->set_state (*child, version); + if (child->name() == Controllable::xml_node_name) { + std::string name; + if (!child->get_property ("name", name)) { + continue; + } + + if (name == _record_enable_control->name()) { + _record_enable_control->set_state (*child, version); + } else if (name == _record_safe_control->name()) { + _record_safe_control->set_state (*child, version); + } else if (name == _monitoring_control->name()) { + _monitoring_control->set_state (*child, version); } } } - - const XMLProperty* prop; - if ((prop = node.property (X_("monitoring"))) != 0) { - _monitoring = MonitorChoice (string_2_enum (prop->value(), _monitoring)); - } else { - _monitoring = MonitorAuto; + if (!node.get_property (X_("saved-meter-point"), _saved_meter_point)) { + _saved_meter_point = _meter_point; } - if ((prop = node.property (X_("saved-meter-point"))) != 0) { - _saved_meter_point = MeterPoint (string_2_enum (prop->value(), _saved_meter_point)); - } else { - _saved_meter_point = _meter_point; + if (!node.get_property (X_("saved-meter-point"), _disk_io_point)) { + _disk_io_point = DiskIOPreFader; + } + + AlignChoice ac; + + if (node.get_property (X_("alignment-choice"), ac)) { + set_align_choice (ac, true); } return 0; @@ -175,42 +263,6 @@ Track::freeze_state() const return _freeze_record.state; } -Track::RecEnableControl::RecEnableControl (boost::shared_ptr t) - : AutomationControl (t->session(), RecEnableAutomation, boost::shared_ptr(), X_("recenable")) - , track (t) -{ - boost::shared_ptr gl(new AutomationList(Evoral::Parameter(RecEnableAutomation))); - set_list (gl); -} - -void -Track::RecEnableControl::set_value (double val) -{ - boost::shared_ptr t = track.lock (); - if (!t) { - return; - } - - t->set_record_enabled (val >= 0.5 ? true : false, this); -} - -double -Track::RecEnableControl::get_value () const -{ - boost::shared_ptr t = track.lock (); - if (!t) { - return 0; - } - - return (t->record_enabled() ? 1.0 : 0.0); -} - -bool -Track::record_enabled () const -{ - return _diskstream && _diskstream->record_enabled (); -} - bool Track::can_record() { @@ -223,33 +275,28 @@ Track::can_record() return will_record; } -void -Track::prep_record_enabled (bool yn, void *src) +int +Track::prep_record_enabled (bool yn) { - if (!_session.writable()) { - return; - } - - if (_freeze_record.state == Frozen) { - return; + if (yn && _record_safe_control->get_value()) { + return -1; } - if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_recenable()) { - _route_group->apply (&Track::prep_record_enabled, yn, _route_group); - return; + if (!can_be_record_enabled()) { + return -1; } /* keep track of the meter point as it was before we rec-enabled */ - if (!_diskstream->record_enabled()) { + if (!_disk_writer->record_enabled()) { _saved_meter_point = _meter_point; } bool will_follow; - + if (yn) { - will_follow = _diskstream->prep_record_enable (); + will_follow = _disk_writer->prep_record_enable (); } else { - will_follow = _diskstream->prep_record_disable (); + will_follow = _disk_writer->prep_record_disable (); } if (will_follow) { @@ -261,27 +308,54 @@ Track::prep_record_enabled (bool yn, void *src) set_meter_point (_saved_meter_point); } } + + return 0; } void -Track::set_record_enabled (bool yn, void *src) +Track::record_enable_changed (bool, Controllable::GroupControlDisposition) { - if (!_session.writable()) { - return; - } + _disk_writer->set_record_enabled (_record_enable_control->get_value()); +} - if (_freeze_record.state == Frozen) { - return; - } +void +Track::record_safe_changed (bool, Controllable::GroupControlDisposition) +{ + _disk_writer->set_record_safe (_record_safe_control->get_value()); +} - if (_route_group && src != _route_group && _route_group->is_active() && _route_group->is_recenable()) { - _route_group->apply (&Track::set_record_enabled, yn, _route_group); - return; - } +bool +Track::can_be_record_safe () +{ + return !_record_enable_control->get_value() && _disk_writer && _session.writable() && (_freeze_record.state != Frozen); +} - _diskstream->set_record_enabled (yn); +bool +Track::can_be_record_enabled () +{ + return !_record_safe_control->get_value() && _disk_writer && !_disk_writer->record_safe() && _session.writable() && (_freeze_record.state != Frozen); +} + +void +Track::parameter_changed (string const & p) +{ + if (p == "track-name-number") { + resync_track_name (); + } + else if (p == "track-name-take") { + resync_track_name (); + } + else if (p == "take-name") { + if (_session.config.get_track_name_take()) { + resync_track_name (); + } + } +} - _rec_enable_control->Changed (); +void +Track::resync_track_name () +{ + set_name(name()); } bool @@ -289,13 +363,41 @@ Track::set_name (const string& str) { bool ret; - if (record_enabled() && _session.actively_recording()) { - /* this messes things up if done while recording */ + if (str.empty ()) { + return false; + } + + if (_record_enable_control->get_value()) { + /* when re-arm'ed the file (named after the track) is already ready to rolll */ return false; } + string diskstream_name = ""; + if (_session.config.get_track_name_take () && !_session.config.get_take_name ().empty()) { + // Note: any text is fine, legalize_for_path() fixes this later + diskstream_name += _session.config.get_take_name (); + diskstream_name += "_"; + } + const int64_t tracknumber = track_number(); + if (tracknumber > 0 && _session.config.get_track_name_number()) { + char num[64], fmt[10]; + snprintf(fmt, sizeof(fmt), "%%0%d" PRId64, _session.track_number_decimals()); + snprintf(num, sizeof(num), fmt, tracknumber); + diskstream_name += num; + diskstream_name += "_"; + } + diskstream_name += str; + + if (diskstream_name == _diskstream_name) { + return true; + } + _diskstream_name = diskstream_name; + + _disk_writer->set_write_source_name (diskstream_name); + boost::shared_ptr me = boost::dynamic_pointer_cast (shared_from_this ()); - if (_diskstream->playlist()->all_regions_empty () && _session.playlists->playlists_for_track (me).size() == 1) { + + if (_playlists[data_type()]->all_regions_empty () && _session.playlists->playlists_for_track (me).size() == 1) { /* Only rename the diskstream (and therefore the playlist) if a) the playlist has never had a region added to it and b) there is only one playlist for this track. @@ -307,7 +409,14 @@ Track::set_name (const string& str) If (b) is not followed, we rename the current playlist and not the other ones, which is a bit confusing (see mantis #4977). */ - _diskstream->set_name (str); + _disk_reader->set_name (str); + _disk_writer->set_name (str); + } + + for (uint32_t n = 0; n < DataType::num_types; ++n) { + if (_playlists[n]) { + _playlists[n]->set_name (str); + } } /* save state so that the statefile fully reflects any filename changes */ @@ -323,7 +432,7 @@ void Track::set_latency_compensation (framecnt_t longest_session_latency) { Route::set_latency_compensation (longest_session_latency); - _diskstream->set_roll_delay (_roll_delay); + _disk_reader->set_roll_delay (_roll_delay); } int @@ -339,7 +448,8 @@ Track::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, /* no outputs? nothing to do ... what happens if we have sends etc. ? */ - if (n_outputs().n_total() == 0) { + if (n_outputs().n_total() == 0 && !ARDOUR::Profile->get_mixbus()) { + //Note: Mixbus has its own output mechanism, so we should operate even if no explicit outputs are assigned return 0; } @@ -347,7 +457,7 @@ Track::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, if (!_active) { silence (nframes); - if (_meter_point == MeterInput && (_monitoring & MonitorInput || _diskstream->record_enabled())) { + if (_meter_point == MeterInput && ((_monitoring_control->monitoring_choice() & MonitorInput) || _disk_writer->record_enabled())) { _meter->reset(); } return 0; @@ -355,8 +465,9 @@ Track::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, if (session_state_changing) { if (_session.transport_speed() != 0.0f) { - /* we're rolling but some state is changing (e.g. our diskstream contents) - so we cannot use them. Be silent till this is over. Don't declick. + /* we're rolling but some state is changing (e.g. our + disk reader contents) so we cannot use them. Be + silent till this is over. Don't declick. XXX note the absurdity of ::no_roll() being called when we ARE rolling! */ @@ -368,40 +479,32 @@ Track::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, */ } - _diskstream->check_record_status (start_frame, can_record); + _disk_writer->check_record_status (start_frame, can_record); bool be_silent; - if (_have_internal_generator) { - /* since the instrument has no input streams, - there is no reason to send any signal - into the route. - */ + MonitorState const s = monitoring_state (); + /* we are not rolling, so be silent even if we are monitoring disk, as there + will be no disk data coming in. + */ + switch (s) { + case MonitoringSilence: be_silent = true; + break; + case MonitoringDisk: + be_silent = true; + break; + case MonitoringInput: + be_silent = false; + break; + default: + be_silent = false; + break; + } - } else { - - MonitorState const s = monitoring_state (); - /* we are not rolling, so be silent even if we are monitoring disk, as there - will be no disk data coming in. - */ - switch (s) { - case MonitoringSilence: - /* if there is an instrument, be_silent should always - be false - */ - be_silent = (the_instrument_unlocked() == 0); - break; - case MonitoringDisk: - be_silent = true; - break; - case MonitoringInput: - be_silent = false; - break; - default: - be_silent = false; - break; - } + //if we have an internal generator, let it play regardless of monitoring state + if (_have_internal_generator) { + be_silent = false; } _amp->apply_gain_automation (false); @@ -413,7 +516,7 @@ Track::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, if (_meter_point == MeterInput) { /* still need input monitoring and metering */ - bool const track_rec = _diskstream->record_enabled (); + bool const track_rec = _disk_writer->record_enabled (); bool const auto_input = _session.config.get_auto_input (); bool const software_monitor = Config->get_monitoring_model() == SoftwareMonitoring; bool const tape_machine_mode = Config->get_tape_machine_mode (); @@ -440,10 +543,10 @@ Track::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, if (no_meter) { BufferSet& bufs (_session.get_silent_buffers (n_process_buffers())); - _meter->run (bufs, 0, 0, nframes, true); - _input->process_input (boost::shared_ptr(), start_frame, end_frame, nframes); + _meter->run (bufs, start_frame, end_frame, 1.0, nframes, true); + _input->process_input (boost::shared_ptr(), start_frame, end_frame, _session.transport_speed(), nframes); } else { - _input->process_input (_meter, start_frame, end_frame, nframes); + _input->process_input (_meter, start_frame, end_frame, _session.transport_speed(), nframes); } } @@ -452,22 +555,17 @@ Track::no_roll (pframes_t nframes, framepos_t start_frame, framepos_t end_frame, } else { BufferSet& bufs = _session.get_route_buffers (n_process_buffers()); - + fill_buffers_with_input (bufs, _input, nframes); if (_meter_point == MeterInput) { - _meter->run (bufs, start_frame, end_frame, nframes, true); + _meter->run (bufs, start_frame, end_frame, _session.transport_speed(), nframes, true); } passthru (bufs, start_frame, end_frame, nframes, false); } - for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - boost::shared_ptr d = boost::dynamic_pointer_cast (*i); - if (d) { - d->flush_buffers (nframes); - } - } + flush_processor_buffers_locked (nframes); return 0; } @@ -477,6 +575,11 @@ Track::silent_roll (pframes_t nframes, framepos_t /*start_frame*/, framepos_t /* { Glib::Threads::RWLock::ReaderLock lm (_processor_lock, Glib::Threads::TRY_LOCK); if (!lm.locked()) { + // XXX DISK reader needs to seek ahead the correct distance ?? OR DOES IT ? + //framecnt_t playback_distance = _disk_reader->calculate_playback_distance(nframes); + //if (can_internal_playback_seek(playback_distance)) { + // internal_playback_seek(playback_distance); + //} return 0; } @@ -493,158 +596,126 @@ Track::silent_roll (pframes_t nframes, framepos_t /*start_frame*/, framepos_t /* _amp->apply_gain_automation(false); silence (nframes); + flush_processor_buffers_locked (nframes); - framecnt_t playback_distance; - - BufferSet& bufs (_session.get_route_buffers (n_process_buffers(), true)); - - int const dret = _diskstream->process (bufs, _session.transport_frame(), nframes, playback_distance, false); - need_butler = _diskstream->commit (playback_distance); - return dret; -} - -void -Track::set_diskstream (boost::shared_ptr ds) -{ - _diskstream = ds; - - ds->PlaylistChanged.connect_same_thread (*this, boost::bind (&Track::diskstream_playlist_changed, this)); - diskstream_playlist_changed (); - ds->RecordEnableChanged.connect_same_thread (*this, boost::bind (&Track::diskstream_record_enable_changed, this)); - ds->SpeedChanged.connect_same_thread (*this, boost::bind (&Track::diskstream_speed_changed, this)); - ds->AlignmentStyleChanged.connect_same_thread (*this, boost::bind (&Track::diskstream_alignment_style_changed, this)); -} - -void -Track::diskstream_playlist_changed () -{ - PlaylistChanged (); /* EMIT SIGNAL */ -} - -void -Track::diskstream_record_enable_changed () -{ - RecordEnableChanged (); /* EMIT SIGNAL */ -} - -void -Track::diskstream_speed_changed () -{ - SpeedChanged (); /* EMIT SIGNAL */ -} - -void -Track::diskstream_alignment_style_changed () -{ - AlignmentStyleChanged (); /* EMIT SIGNAL */ + //BufferSet& bufs (_session.get_route_buffers (n_process_buffers(), true)); + // XXXX DISKWRITER/READER ADVANCE, SET need_butler + return 0; } boost::shared_ptr Track::playlist () { - return _diskstream->playlist (); + return _playlists[data_type()]; } void -Track::request_jack_monitors_input (bool m) +Track::request_input_monitoring (bool m) { - _diskstream->request_jack_monitors_input (m); + for (PortSet::iterator i = _input->ports().begin(); i != _input->ports().end(); ++i) { + AudioEngine::instance()->request_input_monitoring ((*i)->name(), m); + } } void -Track::ensure_jack_monitors_input (bool m) +Track::ensure_input_monitoring (bool m) { - _diskstream->ensure_jack_monitors_input (m); + for (PortSet::iterator i = _input->ports().begin(); i != _input->ports().end(); ++i) { + AudioEngine::instance()->ensure_input_monitoring ((*i)->name(), m); + } } bool Track::destructive () const { - return _diskstream->destructive (); + return _disk_writer->destructive (); } list > & Track::last_capture_sources () { - return _diskstream->last_capture_sources (); + return _disk_writer->last_capture_sources (); } void -Track::set_capture_offset () +Track::update_latency_information () { - _diskstream->set_capture_offset (); + Glib::Threads::RWLock::ReaderLock lr (_processor_lock); + framecnt_t chain_latency = _input->latency (); + + for (ProcessorList::iterator p = _processors.begin(); p != _processors.end(); ++p) { + (*p)->set_input_latency (chain_latency); + chain_latency += (*p)->signal_latency (); + } } -list > -Track::steal_write_sources() +std::string +Track::steal_write_source_name() { - return _diskstream->steal_write_sources (); + return _disk_writer->steal_write_source_name (); } void Track::reset_write_sources (bool r, bool force) { - _diskstream->reset_write_sources (r, force); + _disk_writer->reset_write_sources (r, force); } float Track::playback_buffer_load () const { - return _diskstream->playback_buffer_load (); + return _disk_reader->buffer_load (); } float Track::capture_buffer_load () const { - return _diskstream->capture_buffer_load (); + return _disk_writer->buffer_load (); } int Track::do_refill () { - return _diskstream->do_refill (); + return _disk_reader->do_refill (); } int Track::do_flush (RunContext c, bool force) { - return _diskstream->do_flush (c, force); + return _disk_writer->do_flush (c, force); } void Track::set_pending_overwrite (bool o) { - _diskstream->set_pending_overwrite (o); + _disk_reader->set_pending_overwrite (o); } int Track::seek (framepos_t p, bool complete_refill) { - return _diskstream->seek (p, complete_refill); + if (_disk_reader->seek (p, complete_refill)) { + return -1; + } + return _disk_writer->seek (p, complete_refill); } bool Track::hidden () const { - return _diskstream->hidden (); + return _disk_writer->hidden () || _disk_reader->hidden(); } int Track::can_internal_playback_seek (framecnt_t p) { - return _diskstream->can_internal_playback_seek (p); + return _disk_reader->can_internal_playback_seek (p); } int Track::internal_playback_seek (framecnt_t p) { - return _diskstream->internal_playback_seek (p); -} - -void -Track::non_realtime_input_change () -{ - _diskstream->non_realtime_input_change (); + return _disk_reader->internal_playback_seek (p); } void @@ -656,202 +727,329 @@ Track::non_realtime_locate (framepos_t p) /* don't waste i/o cycles and butler calls for hidden (secret) tracks */ - _diskstream->non_realtime_locate (p); + _disk_reader->non_realtime_locate (p); + _disk_writer->non_realtime_locate (p); } } void -Track::non_realtime_set_speed () +Track::non_realtime_speed_change () { - _diskstream->non_realtime_set_speed (); + _disk_reader->non_realtime_speed_change (); } int Track::overwrite_existing_buffers () { - return _diskstream->overwrite_existing_buffers (); + return _disk_reader->overwrite_existing_buffers (); } framecnt_t Track::get_captured_frames (uint32_t n) const { - return _diskstream->get_captured_frames (n); + return _disk_writer->get_captured_frames (n); } int Track::set_loop (Location* l) { - return _diskstream->set_loop (l); + if (_disk_reader->set_loop (l)) { + return -1; + } + return _disk_writer->set_loop (l); } void Track::transport_looped (framepos_t p) { - _diskstream->transport_looped (p); + return _disk_writer->transport_looped (p); } bool -Track::realtime_set_speed (double s, bool g) +Track::realtime_speed_change () { - return _diskstream->realtime_set_speed (s, g); + if (_disk_reader->realtime_speed_change ()) { + return -1; + } + return _disk_writer->realtime_speed_change (); } void -Track::transport_stopped_wallclock (struct tm & n, time_t t, bool g) +Track::realtime_handle_transport_stopped () { - _diskstream->transport_stopped_wallclock (n, t, g); + Glib::Threads::RWLock::ReaderLock lm (_processor_lock, Glib::Threads::TRY_LOCK); + + if (!lm.locked ()) { + return; + } + + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + (*i)->realtime_handle_transport_stopped (); + } } -bool -Track::pending_overwrite () const +void +Track::transport_stopped_wallclock (struct tm & n, time_t t, bool g) { - return _diskstream->pending_overwrite (); + _disk_writer->transport_stopped_wallclock (n, t, g); } -double -Track::speed () const +bool +Track::pending_overwrite () const { - return _diskstream->speed (); + return _disk_reader->pending_overwrite (); } void -Track::prepare_to_stop (framepos_t p) +Track::prepare_to_stop (framepos_t t, framepos_t a) { - _diskstream->prepare_to_stop (p); + _disk_writer->prepare_to_stop (t, a); } void Track::set_slaved (bool s) { - _diskstream->set_slaved (s); + _disk_reader->set_slaved (s); + _disk_writer->set_slaved (s); } ChanCount Track::n_channels () { - return _diskstream->n_channels (); + return _disk_reader->output_streams(); } framepos_t Track::get_capture_start_frame (uint32_t n) const { - return _diskstream->get_capture_start_frame (n); + return _disk_writer->get_capture_start_frame (n); } AlignStyle Track::alignment_style () const { - return _diskstream->alignment_style (); + return _disk_writer->alignment_style (); } AlignChoice Track::alignment_choice () const { - return _diskstream->alignment_choice (); + return _disk_writer->alignment_choice (); } framepos_t Track::current_capture_start () const { - return _diskstream->current_capture_start (); + return _disk_writer->current_capture_start (); } framepos_t Track::current_capture_end () const { - return _diskstream->current_capture_end (); + return _disk_writer->current_capture_end (); } void Track::playlist_modified () { - _diskstream->playlist_modified (); + _disk_reader->playlist_modified (); +} + +int +Track::find_and_use_playlist (DataType dt, PBD::ID const & id) +{ + boost::shared_ptr playlist; + + if ((playlist = _session.playlists->by_id (id)) == 0) { + return -1; + } + + if (!playlist) { + error << string_compose(_("DiskIOProcessor: \"%1\" isn't an playlist"), id.to_s()) << endmsg; + return -1; + } + + return use_playlist (dt, playlist); } int -Track::use_playlist (boost::shared_ptr p) +Track::use_playlist (DataType dt, boost::shared_ptr p) { - int ret = _diskstream->use_playlist (p); + int ret; + + if ((ret = _disk_reader->use_playlist (dt, p)) == 0) { + if ((ret = _disk_writer->use_playlist (dt, p)) == 0) { + p->set_orig_track_id (id()); + } + } + if (ret == 0) { - p->set_orig_track_id (id()); + _playlists[dt] = p; } + return ret; } int Track::use_copy_playlist () { - int ret = _diskstream->use_copy_playlist (); + assert (_playlists[data_type()]); - if (ret == 0) { - _diskstream->playlist()->set_orig_track_id (id()); + if (_playlists[data_type()] == 0) { + error << string_compose(_("DiskIOProcessor %1: there is no existing playlist to make a copy of!"), _name) << endmsg; + return -1; } - return ret; + string newname; + boost::shared_ptr playlist; + + newname = Playlist::bump_name (_playlists[data_type()]->name(), _session); + + if ((playlist = PlaylistFactory::create (_playlists[data_type()], newname)) == 0) { + return -1; + } + + playlist->reset_shares(); + + return use_playlist (data_type(), playlist); } int Track::use_new_playlist () { - int ret = _diskstream->use_new_playlist (); + string newname; + boost::shared_ptr playlist = _playlists[data_type()]; - if (ret == 0) { - _diskstream->playlist()->set_orig_track_id (id()); + if (playlist) { + newname = Playlist::bump_name (playlist->name(), _session); + } else { + newname = Playlist::bump_name (_name, _session); } - return ret; + playlist = PlaylistFactory::create (data_type(), _session, newname, hidden()); + + if (!playlist) { + return -1; + } + + return use_playlist (data_type(), playlist); } void -Track::set_align_style (AlignStyle s, bool force) +Track::set_align_choice (AlignChoice ac, bool force) { - _diskstream->set_align_style (s, force); + switch (ac) { + case Automatic: + _alignment_choice = Automatic; + set_align_choice_from_io (); + return; + default: + break; + } + + _disk_writer->set_align_choice (ac, force); + _alignment_choice = ac; } void -Track::set_align_choice (AlignChoice s, bool force) +Track::set_align_style (AlignStyle s, bool force) { - _diskstream->set_align_choice (s, force); + _disk_writer->set_align_style (s, force); } -bool -Track::using_diskstream_id (PBD::ID id) const +void +Track::set_align_choice_from_io () { - return (id == _diskstream->id ()); + bool have_physical = false; + + if (_input) { + uint32_t n = 0; + vector connections; + boost::shared_ptr p; + + while (true) { + + p = _input->nth (n++); + + if (!p) { + break; + } + + if (p->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 (_input); + 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) { + _disk_writer->set_align_style (ExistingMaterial); + } else { + _disk_writer->set_align_style (CaptureTime); + } } void Track::set_block_size (pframes_t n) { Route::set_block_size (n); - _diskstream->set_block_size (n); + _disk_reader->set_block_size (n); + _disk_writer->set_block_size (n); } void Track::adjust_playback_buffering () { - if (_diskstream) { - _diskstream->adjust_playback_buffering (); + if (_disk_reader) { + _disk_reader->adjust_buffering (); } } void Track::adjust_capture_buffering () { - if (_diskstream) { - _diskstream->adjust_capture_buffering (); + if (_disk_writer) { + _disk_writer->adjust_buffering (); } } +#ifdef USE_TRACKS_CODE_FEATURES + +/* This is the Tracks version of Track::monitoring_state(). + * + * Ardour developers: try to flag or fix issues if parts of the libardour API + * change in ways that invalidate this + */ + MonitorState Track::monitoring_state () const { /* Explicit requests */ - - if (_monitoring & MonitorInput) { + + if (_monitoring != MonitorInput) { return MonitoringInput; } - + if (_monitoring & MonitorDisk) { return MonitoringDisk; } @@ -860,8 +1058,78 @@ Track::monitoring_state () const I don't think it's ever going to be too pretty too look at. */ + // GZ: NOT USED IN TRACKS + //bool const auto_input = _session.config.get_auto_input (); + //bool const software_monitor = Config->get_monitoring_model() == SoftwareMonitoring; + //bool const tape_machine_mode = Config->get_tape_machine_mode (); + bool const roll = _session.transport_rolling (); bool const track_rec = _diskstream->record_enabled (); + bool session_rec = _session.actively_recording (); + + if (track_rec) { + + if (!session_rec && roll) { + return MonitoringDisk; + } else { + return MonitoringInput; + } + + } else { + + if (roll) { + return MonitoringDisk; + } + } + + return MonitoringSilence; +} + +#else + +/* This is the Ardour/Mixbus version of Track::monitoring_state(). + * + * Tracks developers: do NOT modify this method under any circumstances. + */ + +MonitorState +Track::monitoring_state () const +{ + /* Explicit requests */ + MonitorChoice m (_monitoring_control->monitoring_choice()); + + if (m != MonitorAuto) { + + MonitorState ms ((MonitorState) 0); + + if (m & MonitorInput) { + ms = MonitoringInput; + } + + if (m & MonitorDisk) { + ms = MonitorState (ms | MonitoringDisk); + } + + return ms; + } + + switch (_session.config.get_session_monitoring ()) { + case MonitorDisk: + return MonitoringDisk; + break; + case MonitorInput: + return MonitoringInput; + break; + default: + break; + } + + /* This is an implementation of the truth table in doc/monitor_modes.pdf; + I don't think it's ever going to be too pretty too look at. + */ + + bool const roll = _session.transport_rolling (); + bool const track_rec = _disk_writer->record_enabled (); bool const auto_input = _session.config.get_auto_input (); bool const software_monitor = Config->get_monitoring_model() == SoftwareMonitoring; bool const tape_machine_mode = Config->get_tape_machine_mode (); @@ -871,9 +1139,11 @@ Track::monitoring_state () const * time, but just to keep the semantics the same as they were before * sept 26th 2012, we differentiate between the cases where punch is * enabled and those where it is not. + * + * rg: I suspect this is not the case: monitoring may differ */ - if (_session.config.get_punch_in() || _session.config.get_punch_out()) { + if (_session.config.get_punch_in() || _session.config.get_punch_out() || _session.preroll_record_punch_enabled ()) { session_rec = _session.actively_recording (); } else { session_rec = _session.get_record_enabled(); @@ -900,14 +1170,16 @@ Track::monitoring_state () const } else { return MonitoringDisk; } - + } } - /* NOTREACHED */ + abort(); /* NOTREACHED */ return MonitoringSilence; } +#endif + void Track::maybe_declick (BufferSet& bufs, framecnt_t nframes, int declick) { @@ -917,7 +1189,7 @@ Track::maybe_declick (BufferSet& bufs, framecnt_t nframes, int declick) ditto if we are monitoring inputs. */ - if (_have_internal_generator || monitoring_choice() == MonitorInput) { + if (_have_internal_generator || (_monitoring_control->monitoring_choice() == MonitorInput)) { return; } @@ -963,20 +1235,14 @@ Track::check_initial_delay (framecnt_t nframes, framepos_t& transport_frame) } - return nframes; + return nframes; } void -Track::set_monitoring (MonitorChoice mc) +Track::monitoring_changed (bool, Controllable::GroupControlDisposition) { - if (mc != _monitoring) { - _monitoring = mc; - - for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { - (*i)->monitoring_changed (); - } - - MonitoringChanged (); /* EMIT SIGNAL */ + for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) { + (*i)->monitoring_changed (); } } @@ -986,7 +1252,7 @@ Track::metering_state () const bool rv; if (_session.transport_rolling ()) { // audio_track.cc || midi_track.cc roll() runs meter IFF: - rv = _meter_point == MeterInput && (_monitoring & MonitorInput || _diskstream->record_enabled()); + rv = _meter_point == MeterInput && ((_monitoring_control->monitoring_choice() & MonitorInput) || _disk_writer->record_enabled()); } else { // track no_roll() always metering if rv = _meter_point == MeterInput; @@ -994,3 +1260,319 @@ Track::metering_state () const return rv ? MeteringInput : MeteringRoute; } +bool +Track::set_processor_state (XMLNode const & node, XMLProperty const* prop, ProcessorList& new_order, bool& must_configure) +{ + if (Route::set_processor_state (node, prop, new_order, must_configure)) { + return true; + } + + if (prop->value() == "diskreader") { + if (_disk_reader) { + _disk_reader->set_state (node, Stateful::current_state_version); + new_order.push_back (_disk_reader); + return true; + } + } else if (prop->value() == "diskwriter") { + if (_disk_writer) { + _disk_writer->set_state (node, Stateful::current_state_version); + new_order.push_back (_disk_writer); + return true; + } + } + + error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg; + return false; +} + +void +Track::use_captured_sources (SourceList& srcs, CaptureInfos const & capture_info) +{ + if (srcs.empty()) { + return; + } + + boost::shared_ptr afs = boost::dynamic_pointer_cast (srcs.front()); + boost::shared_ptr mfs = boost::dynamic_pointer_cast (srcs.front()); + + if (afs) { + use_captured_audio_sources (srcs, capture_info); + } + + if (mfs) { + use_captured_midi_sources (srcs, capture_info); + } +} + +void +Track::use_captured_midi_sources (SourceList& srcs, CaptureInfos const & capture_info) +{ + if (srcs.empty() || data_type() != DataType::MIDI) { + return; + } + + boost::shared_ptr mfs = boost::dynamic_pointer_cast (srcs.front()); + boost::shared_ptr pl = _playlists[DataType::MIDI]; + boost::shared_ptr midi_region; + CaptureInfos::const_iterator ci; + + if (!mfs || !pl) { + return; + } + + framecnt_t total_capture = 0; + + for (total_capture = 0, ci = capture_info.begin(); ci != capture_info.end(); ++ci) { + total_capture += (*ci)->frames; + } + + /* 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 (mfs->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)); + + midi_region = boost::dynamic_pointer_cast (rx); + midi_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? */ + } + + pl->clear_changes (); + pl->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; + } + + BeatsFramesConverter converter (_session.tempo_map(), 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, mfs->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)); + midi_region = boost::dynamic_pointer_cast (rx); + if (preroll_off > 0) { + midi_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; + + pl->add_region (midi_region, (*ci)->start + preroll_off); + } + + pl->thaw (); + _session.add_command (new StatefulDiffCommand (pl)); +} + +void +Track::use_captured_audio_sources (SourceList& srcs, CaptureInfos const & capture_info) +{ + if (srcs.empty() || data_type() != DataType::AUDIO) { + return; + } + + boost::shared_ptr afs = boost::dynamic_pointer_cast (srcs.front()); + boost::shared_ptr pl = _playlists[DataType::AUDIO]; + boost::shared_ptr region; + + if (!afs || !pl) { + return; + } + + /* 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 + return; + } + + string whole_file_region_name; + whole_file_region_name = region_name_from_path (afs->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, afs->last_capture_start_frame()); + plist.add (Properties::length, afs->length(0)); + 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 (afs->natural_position()); + } + + + catch (failed_constructor& err) { + error << string_compose(_("%1: could not create region for complete audio file"), _name) << endmsg; + /* XXX what now? */ + } + + pl->clear_changes (); + pl->set_capture_insertion_in_progress (true); + pl->freeze (); + + const framepos_t preroll_off = _session.preroll_record_trim_len (); + framecnt_t buffer_position = afs->last_capture_start_frame (); + CaptureInfos::const_iterator ci; + + for (ci = capture_info.begin(); ci != capture_info.end(); ++ci) { + + string region_name; + + RegionFactory::region_name (region_name, whole_file_region_name, false); + + DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1 capture bufpos %5 start @ %2 length %3 add new region %4\n", + _name, (*ci)->start, (*ci)->frames, region_name, buffer_position)); + + try { + + PropertyList plist; + + plist.add (Properties::start, buffer_position); + plist.add (Properties::length, (*ci)->frames); + plist.add (Properties::name, region_name); + + boost::shared_ptr 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? */ + } + + pl->add_region (region, (*ci)->start + preroll_off, 1, _disk_writer->non_layered()); + pl->set_layer (region, DBL_MAX); + + buffer_position += (*ci)->frames; + } + + pl->thaw (); + pl->set_capture_insertion_in_progress (false); + _session.add_command (new StatefulDiffCommand (pl)); +} + +#ifdef __clang__ +__attribute__((annotate("realtime"))) +#endif +void +Track::setup_invisible_processors_oh_children_of_mine (ProcessorList& processors) +{ + ProcessorList::iterator insert_pos; + + switch (_disk_io_point) { + case DiskIOPreFader: + insert_pos = find (processors.begin(), processors.end(), _trim); + if (insert_pos != processors.end()) { + insert_pos = processors.insert (insert_pos, _disk_writer); + processors.insert (insert_pos, _disk_reader); + } + break; + case DiskIOPostFader: + insert_pos = find (processors.begin(), processors.end(), _main_outs); + if (insert_pos != processors.end()) { + insert_pos = processors.insert (insert_pos, _disk_writer); + processors.insert (insert_pos, _disk_reader); + } + case DiskIOCustom: + break; + } +} + +void +Track::set_disk_io_position (DiskIOPoint diop) +{ + bool display = false; + + switch (diop) { + case DiskIOCustom: + display = true; + break; + default: + display = false; + } + + _disk_writer->set_display_to_user (display); + _disk_reader->set_display_to_user (display); + + const bool changed = (diop != _disk_io_point); + + _disk_io_point = diop; + + if (changed) { + Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); + configure_processors (0); + } +}