This restores a popular feature to use bounce to export ranges with track name file-name, while keeping a dedicated name for the region in the GUI.
1242 lines
32 KiB
C++
1242 lines
32 KiB
C++
/*
|
|
* Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
|
|
* Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
|
|
* Copyright (C) 2007-2019 Paul Davis <paul@linuxaudiosystems.com>
|
|
* Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
|
|
* Copyright (C) 2014-2018 Ben Loftis <ben@harrisonconsoles.com>
|
|
* Copyright (C) 2016 Julien "_FrnchFrgg_" RIVAUD <frnchfrgg@free.fr>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "pbd/error.h"
|
|
|
|
#include "ardour/amp.h"
|
|
#include "ardour/audioengine.h"
|
|
#include "ardour/audiofilesource.h"
|
|
#include "ardour/audioplaylist.h"
|
|
#include "ardour/audioregion.h"
|
|
#include "ardour/debug.h"
|
|
#include "ardour/delivery.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_playlist.h"
|
|
#include "ardour/midi_region.h"
|
|
#include "ardour/monitor_control.h"
|
|
#include "ardour/playlist.h"
|
|
#include "ardour/playlist_factory.h"
|
|
#include "ardour/polarity_processor.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/triggerbox.h"
|
|
#include "ardour/types_convert.h"
|
|
#include "ardour/utils.h"
|
|
|
|
#include "pbd/i18n.h"
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
|
|
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)
|
|
, _mode (mode)
|
|
, _alignment_choice (Automatic)
|
|
, _pending_name_change (false)
|
|
{
|
|
_freeze_record.state = NoFreeze;
|
|
}
|
|
|
|
Track::~Track ()
|
|
{
|
|
DEBUG_TRACE (DEBUG::Destruction, string_compose ("track %1 destructor\n", _name));
|
|
|
|
for (auto const& p : _playlists) {
|
|
if (p) {
|
|
p->clear_time_domain_parent ();
|
|
}
|
|
}
|
|
|
|
if (_disk_reader) {
|
|
_disk_reader.reset ();
|
|
}
|
|
|
|
if (_disk_writer) {
|
|
_disk_writer.reset ();
|
|
}
|
|
}
|
|
|
|
int
|
|
Track::init ()
|
|
{
|
|
if (!is_auditioner()) {
|
|
_triggerbox = std::shared_ptr<TriggerBox> (new TriggerBox (_session, data_type ()));
|
|
_triggerbox->set_owner (this);
|
|
}
|
|
|
|
if (Route::init ()) {
|
|
return -1;
|
|
}
|
|
|
|
DiskIOProcessor::Flag dflags = DiskIOProcessor::Recordable;
|
|
|
|
_disk_reader.reset (new DiskReader (_session, *this, name(), Temporal::TimeDomainProvider (Config->get_default_automation_time_domain()), dflags));
|
|
_disk_reader->set_block_size (_session.get_block_size ());
|
|
_disk_reader->set_owner (this);
|
|
|
|
_disk_writer.reset (new DiskWriter (_session, *this, name(), dflags));
|
|
_disk_writer->set_block_size (_session.get_block_size ());
|
|
_disk_writer->set_owner (this);
|
|
|
|
/* no triggerbox for the auditioner, to avoid visual clutter in
|
|
* patchbays and elsewhere (or special-case code in those places)
|
|
*/
|
|
|
|
set_align_choice_from_io ();
|
|
|
|
std::shared_ptr<Route> rp (std::dynamic_pointer_cast<Route> (shared_from_this()));
|
|
std::shared_ptr<Track> rt = std::dynamic_pointer_cast<Track> (rp);
|
|
|
|
_record_enable_control.reset (new RecordEnableControl (_session, EventTypeMap::instance().to_symbol (RecEnableAutomation), *this, *this));
|
|
add_control (_record_enable_control);
|
|
|
|
_record_safe_control.reset (new RecordSafeControl (_session, EventTypeMap::instance().to_symbol (RecSafeAutomation), *this, *this));
|
|
add_control (_record_safe_control);
|
|
|
|
_monitoring_control.reset (new MonitorControl (_session, EventTypeMap::instance().to_symbol (MonitoringAutomation), *this, *this));
|
|
add_control (_monitoring_control);
|
|
|
|
if (!name().empty()) {
|
|
/* an empty name means that we are being constructed via
|
|
* serialized state (XML). Don't create a playlist, because one
|
|
* will be created or discovered during ::set_state().
|
|
*/
|
|
use_new_playlist (data_type());
|
|
/* set disk-I/O and diskstream name */
|
|
set_name (name ());
|
|
}
|
|
|
|
_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));
|
|
|
|
_disk_reader->ConfigurationChanged.connect_same_thread (*this, boost::bind (&Track::chan_count_changed, this));
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Track::input_changed ()
|
|
{
|
|
if (_disk_writer && _alignment_choice == Automatic) {
|
|
set_align_choice_from_io ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::chan_count_changed ()
|
|
{
|
|
ChanCountChanged (); /* EMIT SIGNAL */
|
|
}
|
|
|
|
XMLNode&
|
|
Track::playlist_state () const
|
|
{
|
|
XMLNode* node = new XMLNode("Route");
|
|
node->set_property("version", CURRENT_SESSION_FILE_VERSION);
|
|
|
|
if (_playlists[DataType::AUDIO]) {
|
|
node->set_property (X_("audio-playlist"), _playlists[DataType::AUDIO]->id().to_s());
|
|
}
|
|
|
|
if (_playlists[DataType::MIDI]) {
|
|
node->set_property (X_("midi-playlist"), _playlists[DataType::MIDI]->id().to_s());
|
|
}
|
|
|
|
return *node;
|
|
}
|
|
|
|
XMLNode&
|
|
Track::state (bool save_template) const
|
|
{
|
|
XMLNode& root (Route::state (save_template));
|
|
|
|
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_("alignment-choice"), _alignment_choice);
|
|
|
|
return root;
|
|
}
|
|
|
|
int
|
|
Track::set_state (const XMLNode& node, int version)
|
|
{
|
|
if (Route::set_state (node, version)) {
|
|
return -1;
|
|
}
|
|
|
|
if (version >= 3000 && version < 6000) {
|
|
if (XMLNode* ds_node = find_named_node (node, "Diskstream")) {
|
|
std::string name;
|
|
if (ds_node->get_property ("playlist", name)) {
|
|
|
|
ds_node->set_property ("active", true);
|
|
|
|
_disk_writer->set_state (*ds_node, version);
|
|
_disk_reader->set_state (*ds_node, version);
|
|
|
|
AlignChoice ac;
|
|
if (ds_node->get_property (X_("capture-alignment"), ac)) {
|
|
set_align_choice (ac, true);
|
|
}
|
|
|
|
if (std::shared_ptr<AudioPlaylist> pl = std::dynamic_pointer_cast<AudioPlaylist> (_session.playlists()->by_name (name))) {
|
|
use_playlist (DataType::AUDIO, pl);
|
|
}
|
|
|
|
if (std::shared_ptr<MidiPlaylist> pl = std::dynamic_pointer_cast<MidiPlaylist> (_session.playlists()->by_name (name))) {
|
|
use_playlist (DataType::MIDI, pl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
XMLNode* child;
|
|
std::string playlist_id;
|
|
|
|
if (node.get_property (X_("audio-playlist"), playlist_id)) {
|
|
find_and_use_playlist (DataType::AUDIO, PBD::ID (playlist_id));
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!node.get_property (X_("saved-meter-point"), _saved_meter_point)) {
|
|
_saved_meter_point = _meter_point;
|
|
}
|
|
|
|
|
|
AlignChoice ac;
|
|
|
|
if (node.get_property (X_("alignment-choice"), ac)) {
|
|
set_align_choice (ac, true);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Track::FreezeRecord::~FreezeRecord ()
|
|
{
|
|
for (vector<FreezeRecordProcessorInfo*>::iterator i = processor_info.begin(); i != processor_info.end(); ++i) {
|
|
delete *i;
|
|
}
|
|
}
|
|
|
|
Track::FreezeState
|
|
Track::freeze_state() const
|
|
{
|
|
return _freeze_record.state;
|
|
}
|
|
|
|
bool
|
|
Track::declick_in_progress () const
|
|
{
|
|
return active() && _disk_reader->declick_in_progress ();
|
|
}
|
|
|
|
bool
|
|
Track::can_record()
|
|
{
|
|
bool will_record = true;
|
|
for (PortSet::iterator i = _input->ports().begin(); i != _input->ports().end() && will_record; ++i) {
|
|
if (!i->connected())
|
|
will_record = false;
|
|
}
|
|
|
|
return will_record;
|
|
}
|
|
|
|
int
|
|
Track::prep_record_enabled (bool yn)
|
|
{
|
|
if (yn && _record_safe_control->get_value()) {
|
|
return -1;
|
|
}
|
|
|
|
if (!can_be_record_enabled()) {
|
|
return -1;
|
|
}
|
|
|
|
/* keep track of the meter point as it was before we rec-enabled */
|
|
if (!_disk_writer->record_enabled()) {
|
|
_saved_meter_point = _meter_point;
|
|
}
|
|
|
|
bool will_follow;
|
|
|
|
if (yn) {
|
|
will_follow = _disk_writer->prep_record_enable ();
|
|
} else {
|
|
will_follow = _disk_writer->prep_record_disable ();
|
|
}
|
|
|
|
if (will_follow) {
|
|
if (yn) {
|
|
if (_meter_point != MeterCustom) {
|
|
set_meter_point (MeterInput);
|
|
}
|
|
} else {
|
|
set_meter_point (_saved_meter_point);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Track::record_enable_changed (bool, Controllable::GroupControlDisposition)
|
|
{
|
|
_disk_writer->set_record_enabled (_record_enable_control->get_value());
|
|
}
|
|
|
|
void
|
|
Track::record_safe_changed (bool, Controllable::GroupControlDisposition)
|
|
{
|
|
_disk_writer->set_record_safe (_record_safe_control->get_value());
|
|
}
|
|
|
|
bool
|
|
Track::can_be_record_safe ()
|
|
{
|
|
return !_record_enable_control->get_value() && _disk_writer && _session.writable() && (_freeze_record.state != Frozen);
|
|
}
|
|
|
|
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_take_name ();
|
|
}
|
|
else if (p == "track-name-take") {
|
|
resync_take_name ();
|
|
}
|
|
else if (p == "take-name") {
|
|
if (_session.config.get_track_name_take()) {
|
|
resync_take_name ();
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
Track::resync_take_name (std::string n)
|
|
{
|
|
if (n.empty ()) {
|
|
n = name ();
|
|
}
|
|
|
|
if (_record_enable_control->get_value() && _session.actively_recording ()) {
|
|
_pending_name_change = true;
|
|
return -1;
|
|
}
|
|
|
|
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 += n;
|
|
|
|
if (diskstream_name == _diskstream_name) {
|
|
return 1;
|
|
}
|
|
|
|
_diskstream_name = diskstream_name;
|
|
_disk_writer->set_write_source_name (diskstream_name);
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
Track::set_name (const string& str)
|
|
{
|
|
if (str.empty ()) {
|
|
return false;
|
|
}
|
|
|
|
switch (resync_take_name (str)) {
|
|
case -1:
|
|
return false;
|
|
case 1:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
std::shared_ptr<Track> me = std::dynamic_pointer_cast<Track> (shared_from_this ());
|
|
|
|
_disk_reader->set_name (str);
|
|
_disk_writer->set_name (str);
|
|
|
|
|
|
/* When creating a track during session-load, do not change playlist's name.
|
|
*
|
|
* Changing the playlist name from 'toBeResetFroXML' breaks loading
|
|
* Ardour v2..5 sessions. Older versions of Arodur identified playlist
|
|
* by name, and this causes duplicate names and name conflicts.
|
|
* (new track name -> new playlist name != old playlist)
|
|
*/
|
|
if (_session.loading ()) {
|
|
return Route::set_name (str);
|
|
}
|
|
|
|
for (uint32_t n = 0; n < DataType::num_types; ++n) {
|
|
if (!_playlists[n]) {
|
|
continue;
|
|
}
|
|
if (_playlists[n]->all_regions_empty () && _session.playlists()->playlists_for_track (me).size() == 1) {
|
|
/* Only rename the the playlist if
|
|
* a) the playlist has never had a region added to it and
|
|
* b) there is only one playlist for this track.
|
|
*
|
|
* If (a) is not followed, people can get confused if, say,
|
|
* they have notes about a playlist with a given name and then
|
|
* it changes (see mantis #4759).
|
|
*
|
|
* If (b) is not followed, we rename the current playlist and not
|
|
* the other ones, which is a bit confusing (see mantis #4977).
|
|
*/
|
|
_playlists[n]->set_name (str);
|
|
}
|
|
}
|
|
|
|
return Route::set_name (str);
|
|
}
|
|
|
|
std::shared_ptr<Playlist>
|
|
Track::playlist ()
|
|
{
|
|
return _playlists[data_type()];
|
|
}
|
|
|
|
void
|
|
Track::request_input_monitoring (bool m)
|
|
{
|
|
for (PortSet::iterator i = _input->ports().begin(); i != _input->ports().end(); ++i) {
|
|
AudioEngine::instance()->request_input_monitoring ((*i)->name(), m);
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::ensure_input_monitoring (bool m)
|
|
{
|
|
for (PortSet::iterator i = _input->ports().begin(); i != _input->ports().end(); ++i) {
|
|
AudioEngine::instance()->ensure_input_monitoring ((*i)->name(), m);
|
|
}
|
|
}
|
|
|
|
list<std::shared_ptr<Source> > &
|
|
Track::last_capture_sources ()
|
|
{
|
|
return _disk_writer->last_capture_sources ();
|
|
}
|
|
|
|
std::string
|
|
Track::steal_write_source_name()
|
|
{
|
|
return _disk_writer->steal_write_source_name ();
|
|
}
|
|
|
|
void
|
|
Track::reset_write_sources (bool r, bool force)
|
|
{
|
|
_disk_writer->reset_write_sources (r, force);
|
|
}
|
|
|
|
float
|
|
Track::playback_buffer_load () const
|
|
{
|
|
return _disk_reader->buffer_load ();
|
|
}
|
|
|
|
float
|
|
Track::capture_buffer_load () const
|
|
{
|
|
return _disk_writer->buffer_load ();
|
|
}
|
|
|
|
int
|
|
Track::do_refill ()
|
|
{
|
|
return _disk_reader->do_refill ();
|
|
}
|
|
|
|
int
|
|
Track::do_flush (RunContext c, bool force)
|
|
{
|
|
return _disk_writer->do_flush (c, force);
|
|
}
|
|
|
|
void
|
|
Track::set_pending_overwrite (OverwriteReason why)
|
|
{
|
|
_disk_reader->set_pending_overwrite (why);
|
|
}
|
|
|
|
int
|
|
Track::seek (samplepos_t p, bool complete_refill)
|
|
{
|
|
if (_disk_reader->seek (p, complete_refill)) {
|
|
return -1;
|
|
}
|
|
return _disk_writer->seek (p, complete_refill);
|
|
}
|
|
|
|
bool
|
|
Track::can_internal_playback_seek (samplecnt_t p)
|
|
{
|
|
return _disk_reader->can_internal_playback_seek (p);
|
|
}
|
|
|
|
void
|
|
Track::internal_playback_seek (samplecnt_t p)
|
|
{
|
|
return _disk_reader->internal_playback_seek (p);
|
|
}
|
|
|
|
void
|
|
Track::non_realtime_locate (samplepos_t p)
|
|
{
|
|
Route::non_realtime_locate (p);
|
|
}
|
|
|
|
bool
|
|
Track::overwrite_existing_buffers ()
|
|
{
|
|
return _disk_reader->overwrite_existing_buffers ();
|
|
}
|
|
|
|
samplecnt_t
|
|
Track::get_captured_samples (uint32_t n) const
|
|
{
|
|
return _disk_writer->get_captured_samples (n);
|
|
}
|
|
|
|
void
|
|
Track::transport_looped (samplepos_t p)
|
|
{
|
|
return _disk_writer->transport_looped (p);
|
|
}
|
|
|
|
void
|
|
Track::transport_stopped_wallclock (struct tm & n, time_t t, bool g)
|
|
{
|
|
_disk_writer->transport_stopped_wallclock (n, t, g);
|
|
|
|
if (_pending_name_change) {
|
|
resync_take_name ();
|
|
_pending_name_change = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::mark_capture_xrun ()
|
|
{
|
|
if (_disk_writer->record_enabled ()) {
|
|
_disk_writer->mark_capture_xrun ();
|
|
}
|
|
}
|
|
|
|
bool
|
|
Track::pending_overwrite () const
|
|
{
|
|
return _disk_reader->pending_overwrite ();
|
|
}
|
|
|
|
void
|
|
Track::set_slaved (bool s)
|
|
{
|
|
_disk_reader->set_slaved (s);
|
|
_disk_writer->set_slaved (s);
|
|
}
|
|
|
|
ChanCount
|
|
Track::n_channels ()
|
|
{
|
|
return _disk_reader->output_streams();
|
|
}
|
|
|
|
samplepos_t
|
|
Track::get_capture_start_sample (uint32_t n) const
|
|
{
|
|
return _disk_writer->get_capture_start_sample (n);
|
|
}
|
|
|
|
AlignStyle
|
|
Track::alignment_style () const
|
|
{
|
|
return _disk_writer->alignment_style ();
|
|
}
|
|
|
|
AlignChoice
|
|
Track::alignment_choice () const
|
|
{
|
|
return _alignment_choice;
|
|
}
|
|
|
|
samplepos_t
|
|
Track::current_capture_start () const
|
|
{
|
|
return _disk_writer->current_capture_start ();
|
|
}
|
|
|
|
samplepos_t
|
|
Track::current_capture_end () const
|
|
{
|
|
return _disk_writer->current_capture_end ();
|
|
}
|
|
|
|
void
|
|
Track::playlist_modified ()
|
|
{
|
|
_disk_reader->playlist_modified ();
|
|
}
|
|
|
|
int
|
|
Track::find_and_use_playlist (DataType dt, PBD::ID const & id)
|
|
{
|
|
std::shared_ptr<Playlist> 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 (DataType dt, std::shared_ptr<Playlist> p, bool set_orig)
|
|
{
|
|
int ret;
|
|
|
|
if ((ret = _disk_reader->use_playlist (dt, p)) == 0) {
|
|
if ((ret = _disk_writer->use_playlist (dt, p)) == 0) {
|
|
if (set_orig) {
|
|
p->set_orig_track_id (id());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Playlist> old = _playlists[dt];
|
|
|
|
if (ret == 0) {
|
|
_playlists[dt] = p;
|
|
}
|
|
|
|
if (old) {
|
|
std::shared_ptr<RegionList> rl (new RegionList (old->region_list_property ().rlist ()));
|
|
if (rl->size () > 0) {
|
|
Region::RegionsPropertyChanged (rl, Properties::hidden);
|
|
}
|
|
/* we don't know for certain that we controlled the old
|
|
* playlist's time domain, but it's a pretty good guess. If it
|
|
* has an actual parent, revert to using its parent's domain
|
|
*/
|
|
if (old->time_domain_parent()) {
|
|
old->clear_time_domain_parent ();
|
|
}
|
|
}
|
|
|
|
if (p) {
|
|
std::shared_ptr<RegionList> rl (new RegionList (p->region_list_property ().rlist ()));
|
|
if (rl->size () > 0) {
|
|
Region::RegionsPropertyChanged (rl, Properties::hidden);
|
|
}
|
|
|
|
/* If the playlist has no time domain parent or its parent is
|
|
* the session, reset to the explicit time domain of this
|
|
* track.
|
|
*/
|
|
|
|
if (!p->time_domain_parent() || p->time_domain_parent() == &_session) {
|
|
/* XXX DANGER : track could go away leaving playlist
|
|
* with dead parent time domain provider
|
|
*/
|
|
p->set_time_domain_parent (*this);
|
|
}
|
|
}
|
|
|
|
_session.set_dirty ();
|
|
PlaylistChanged (); /* EMIT SIGNAL */
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
Track::use_copy_playlist ()
|
|
{
|
|
assert (_playlists[data_type()]);
|
|
|
|
if (_playlists[data_type()] == 0) {
|
|
error << string_compose(_("DiskIOProcessor %1: there is no existing playlist to make a copy of!"), _name) << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
string newname;
|
|
std::shared_ptr<Playlist> playlist;
|
|
|
|
newname = Playlist::bump_name (_playlists[data_type()]->name(), _session);
|
|
|
|
if ((playlist = PlaylistFactory::create (_playlists[data_type()], newname)) == 0) {
|
|
return -1;
|
|
}
|
|
|
|
playlist->reset_shares();
|
|
|
|
int rv = use_playlist (data_type(), playlist);
|
|
PlaylistAdded (); /* EMIT SIGNAL */
|
|
return rv;
|
|
}
|
|
|
|
int
|
|
Track::use_new_playlist (DataType dt)
|
|
{
|
|
string newname;
|
|
std::shared_ptr<Playlist> playlist = _playlists[dt];
|
|
|
|
if (playlist) {
|
|
newname = Playlist::bump_name (playlist->name(), _session);
|
|
} else {
|
|
newname = Playlist::bump_name (_name, _session);
|
|
}
|
|
|
|
playlist = PlaylistFactory::create (dt, _session, newname, is_private_route());
|
|
|
|
if (!playlist) {
|
|
return -1;
|
|
}
|
|
|
|
int rv = use_playlist (dt, playlist);
|
|
PlaylistAdded (); /* EMIT SIGNAL */
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
Track::set_align_choice (AlignChoice ac, bool force)
|
|
{
|
|
_alignment_choice = ac;
|
|
switch (ac) {
|
|
case Automatic:
|
|
set_align_choice_from_io ();
|
|
break;
|
|
case UseCaptureTime:
|
|
_disk_writer->set_align_style (CaptureTime, force);
|
|
break;
|
|
case UseExistingMaterial:
|
|
_disk_writer->set_align_style (ExistingMaterial, force);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::set_align_style (AlignStyle s, bool force)
|
|
{
|
|
_disk_writer->set_align_style (s, force);
|
|
}
|
|
|
|
void
|
|
Track::set_align_choice_from_io ()
|
|
{
|
|
bool have_physical = false;
|
|
|
|
if (_input) {
|
|
uint32_t n = 0;
|
|
std::shared_ptr<Port> p;
|
|
|
|
while (0 != (p = _input->nth (n++))) {
|
|
/* In case of JACK all ports not owned by Ardour may be re-sampled,
|
|
* and latency is added. External JACK ports need to be treated
|
|
* like physical ports: I/O latency needs to be taken into account.
|
|
*
|
|
* When not using JACK, all external ports are physical ports
|
|
* so this is a NO-OP for other backends.
|
|
*/
|
|
if (p->externally_connected () || p->physically_connected ()) {
|
|
have_physical = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#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);
|
|
_disk_reader->set_block_size (n);
|
|
_disk_writer->set_block_size (n);
|
|
}
|
|
|
|
void
|
|
Track::adjust_playback_buffering ()
|
|
{
|
|
if (_disk_reader) {
|
|
_disk_reader->adjust_buffering ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::adjust_capture_buffering ()
|
|
{
|
|
if (_disk_writer) {
|
|
_disk_writer->adjust_buffering ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::monitoring_changed (bool, Controllable::GroupControlDisposition)
|
|
{
|
|
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
|
|
(*i)->monitoring_changed ();
|
|
}
|
|
}
|
|
|
|
bool
|
|
Track::set_processor_state (XMLNode const& node, int version, XMLProperty const* prop, ProcessorList& new_order, bool& must_configure)
|
|
{
|
|
if (Route::set_processor_state (node, version, prop, new_order, must_configure)) {
|
|
return true;
|
|
}
|
|
|
|
cerr << name() << " looking for state for track procs, DR = " << _disk_reader << endl;
|
|
|
|
if (prop->value() == "diskreader") {
|
|
if (_disk_reader) {
|
|
_disk_reader->set_state (node, version);
|
|
new_order.push_back (_disk_reader);
|
|
return true;
|
|
}
|
|
} else if (prop->value() == "diskwriter") {
|
|
if (_disk_writer) {
|
|
_disk_writer->set_state (node, version);
|
|
new_order.push_back (_disk_writer);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg;
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<Region>
|
|
Track::bounce (InterThreadInfo& itt, std::string const& name)
|
|
{
|
|
return bounce_range (_session.current_start_sample(), _session.current_end_sample(), itt, main_outs(), false, name);
|
|
}
|
|
|
|
std::shared_ptr<Region>
|
|
Track::bounce_range (samplepos_t start,
|
|
samplepos_t end,
|
|
InterThreadInfo& itt,
|
|
std::shared_ptr<Processor> endpoint,
|
|
bool include_endpoint,
|
|
std::string const& nm, bool prefix_track_name)
|
|
{
|
|
std::string source_name;
|
|
|
|
if (prefix_track_name && nm.length() > 0) {
|
|
source_name = string_compose ("%1 - %2", name(), nm);
|
|
} else {
|
|
source_name = nm;
|
|
}
|
|
|
|
vector<std::shared_ptr<Source> > srcs;
|
|
return _session.write_one_track (*this, start, end, false, srcs, itt, endpoint, include_endpoint, false, false, source_name, nm);
|
|
}
|
|
|
|
void
|
|
Track::use_captured_sources (SourceList& srcs, CaptureInfos const & capture_info)
|
|
{
|
|
if (srcs.empty()) {
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<AudioFileSource> afs = std::dynamic_pointer_cast<AudioFileSource> (srcs.front());
|
|
std::shared_ptr<SMFSource> mfs = std::dynamic_pointer_cast<SMFSource> (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;
|
|
}
|
|
|
|
/* There is an assumption here that we have only a single MIDI file */
|
|
|
|
std::shared_ptr<SMFSource> mfs = std::dynamic_pointer_cast<SMFSource> (srcs.front());
|
|
std::shared_ptr<Playlist> pl = _playlists[DataType::MIDI];
|
|
std::shared_ptr<MidiRegion> midi_region;
|
|
CaptureInfos::const_iterator ci;
|
|
|
|
if (!mfs || !pl) {
|
|
return;
|
|
}
|
|
|
|
RecordMode rmode = _session.config.get_record_mode ();
|
|
|
|
samplecnt_t total_capture = 0;
|
|
|
|
for (total_capture = 0, ci = capture_info.begin(); ci != capture_info.end(); ++ci) {
|
|
total_capture += (*ci)->samples;
|
|
}
|
|
|
|
/* 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::opaque, rmode != RecSoundOnSound);
|
|
plist.add (Properties::start, timecnt_t (Temporal::BeatTime));
|
|
plist.add (Properties::length, mfs->length());
|
|
plist.add (Properties::layer, 0);
|
|
|
|
std::shared_ptr<Region> rx (RegionFactory::create (srcs, plist));
|
|
|
|
midi_region = std::dynamic_pointer_cast<MidiRegion> (rx);
|
|
midi_region->special_set_position (timepos_t (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 sample time of the initial capture in this pass, which is where the source starts */
|
|
samplepos_t initial_capture = 0;
|
|
if (!capture_info.empty()) {
|
|
initial_capture = capture_info.front()->start;
|
|
}
|
|
|
|
const samplepos_t preroll_off = _session.preroll_record_trim_len ();
|
|
const timepos_t cstart (timepos_t (capture_info.front()->start).beats());
|
|
|
|
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)->samples, region_name));
|
|
|
|
|
|
// cerr << _name << ": based on ci of " << (*ci)->start << " for " << (*ci)->samples << " start: " << (*ci)->loop_offset << " add MIDI 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 */
|
|
samplecnt_t start_off = (*ci)->start - initial_capture + (*ci)->loop_offset;
|
|
timepos_t s;
|
|
timecnt_t l;
|
|
|
|
if (time_domain() == Temporal::BeatTime) {
|
|
|
|
const timepos_t ss (start_off);
|
|
const timecnt_t ll ((*ci)->samples, ss);
|
|
|
|
s = timepos_t (ss.beats());
|
|
l = timecnt_t (ll.beats(), s);
|
|
|
|
} else {
|
|
|
|
s = timepos_t (start_off);
|
|
l = timecnt_t ((*ci)->samples, s);
|
|
}
|
|
|
|
plist.add (Properties::start, s);
|
|
plist.add (Properties::length, l);
|
|
plist.add (Properties::opaque, rmode != RecSoundOnSound);
|
|
plist.add (Properties::name, region_name);
|
|
plist.add (Properties::reg_group, Region::get_retained_group_id());
|
|
|
|
std::shared_ptr<Region> rx (RegionFactory::create (srcs, plist));
|
|
midi_region = std::dynamic_pointer_cast<MidiRegion> (rx);
|
|
if (preroll_off > 0) {
|
|
midi_region->trim_front (timepos_t ((*ci)->start - initial_capture + preroll_off));
|
|
}
|
|
}
|
|
|
|
catch (failed_constructor& err) {
|
|
error << string_compose (_("%1: could not create region for captured data!"), name()) << endmsg;
|
|
continue; /* XXX is this OK? */
|
|
}
|
|
|
|
if (time_domain() == Temporal::BeatTime) {
|
|
const timepos_t b ((*ci)->start + preroll_off);
|
|
pl->add_region (midi_region, timepos_t (b.beats()), 1, rmode == RecNonLayered);
|
|
} else {
|
|
pl->add_region (midi_region, timepos_t ((*ci)->start + preroll_off), 1, rmode == RecNonLayered);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
std::shared_ptr<AudioFileSource> afs = std::dynamic_pointer_cast<AudioFileSource> (srcs.front());
|
|
std::shared_ptr<Playlist> pl = _playlists[DataType::AUDIO];
|
|
std::shared_ptr<AudioRegion> region;
|
|
|
|
if (!afs || !pl) {
|
|
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!)
|
|
*/
|
|
|
|
RecordMode rmode = _session.config.get_record_mode ();
|
|
|
|
try {
|
|
PropertyList plist;
|
|
|
|
plist.add (Properties::start, timecnt_t (afs->last_capture_start_sample(), timepos_t (Temporal::AudioTime)));
|
|
plist.add (Properties::length, afs->length());
|
|
plist.add (Properties::name, whole_file_region_name);
|
|
plist.add (Properties::opaque, rmode != RecSoundOnSound);
|
|
std::shared_ptr<Region> rx (RegionFactory::create (srcs, plist));
|
|
rx->set_automatic (true);
|
|
rx->set_whole_file (true);
|
|
|
|
region = std::dynamic_pointer_cast<AudioRegion> (rx);
|
|
region->special_set_position (timepos_t (afs->natural_position()));
|
|
}
|
|
|
|
|
|
catch (failed_constructor& err) {
|
|
error << string_compose(_("%1: could not create region for complete audio file"), _name) << endmsg;
|
|
/* XXX what now? */
|
|
}
|
|
|
|
/* If this playlist doesn't already have a pgroup (a new track won't) then
|
|
* assign it one, using the take-id of the first recording)
|
|
*/
|
|
if (pl->pgroup_id().length() == 0) {
|
|
pl->set_pgroup_id (afs->take_id ());
|
|
}
|
|
|
|
pl->clear_changes ();
|
|
pl->set_capture_insertion_in_progress (true);
|
|
pl->freeze ();
|
|
|
|
const samplepos_t preroll_off = _session.preroll_record_trim_len ();
|
|
samplecnt_t buffer_position = afs->last_capture_start_sample ();
|
|
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)->samples, region_name, buffer_position));
|
|
|
|
try {
|
|
|
|
PropertyList plist;
|
|
|
|
plist.add (Properties::start, timecnt_t (buffer_position, timepos_t::zero (false)));
|
|
plist.add (Properties::length, timecnt_t ((*ci)->samples, timepos_t::zero (false)));
|
|
plist.add (Properties::name, region_name);
|
|
plist.add (Properties::opaque, rmode != RecSoundOnSound);
|
|
plist.add (Properties::reg_group, Region::get_retained_group_id());
|
|
|
|
std::shared_ptr<Region> rx (RegionFactory::create (srcs, plist));
|
|
region = std::dynamic_pointer_cast<AudioRegion> (rx);
|
|
if (preroll_off > 0) {
|
|
region->trim_front (timepos_t (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, timepos_t ((*ci)->start + preroll_off), 1, RecNonLayered == rmode);
|
|
pl->set_layer (region, DBL_MAX);
|
|
|
|
buffer_position += (*ci)->samples;
|
|
}
|
|
|
|
pl->thaw ();
|
|
pl->set_capture_insertion_in_progress (false);
|
|
_session.add_command (new StatefulDiffCommand (pl));
|
|
}
|
|
|
|
void
|
|
Track::time_domain_changed ()
|
|
{
|
|
Route::time_domain_changed ();
|
|
|
|
std::shared_ptr<Playlist> pl = _playlists[DataType::AUDIO];
|
|
if (pl) {
|
|
if (pl->time_domain_parent() == this) {
|
|
pl->time_domain_changed ();
|
|
}
|
|
}
|
|
pl = _playlists[DataType::MIDI];
|
|
if (pl) {
|
|
if (pl->time_domain_parent() == this) {
|
|
pl->time_domain_changed ();
|
|
}
|
|
}
|
|
}
|