mirror of
https://github.com/Ardour/ardour.git
synced 2026-01-03 12:19:33 +01:00
This moves common code (get and fill buffers) into ::passthru() and renames ::passthru() to ::run_route(). passthru_silence() is no longer used (it was only needed A5 style Track::no_roll_unlocked for no-roll + disk-monitoring)
1153 lines
28 KiB
C++
1153 lines
28 KiB
C++
/*
|
|
Copyright (C) 2006 Paul Davis
|
|
|
|
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., 675 Mass Ave, Cambridge, MA 02139, 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/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 "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)
|
|
{
|
|
_freeze_record.state = NoFreeze;
|
|
_declickable = true;
|
|
|
|
}
|
|
|
|
Track::~Track ()
|
|
{
|
|
DEBUG_TRACE (DEBUG::Destruction, string_compose ("track %1 destructor\n", _name));
|
|
|
|
if (_disk_reader) {
|
|
_disk_reader->set_route (boost::shared_ptr<Route>());
|
|
_disk_reader.reset ();
|
|
}
|
|
|
|
if (_disk_writer) {
|
|
_disk_writer->set_route (boost::shared_ptr<Route>());
|
|
_disk_writer.reset ();
|
|
}
|
|
}
|
|
|
|
int
|
|
Track::init ()
|
|
{
|
|
if (Route::init ()) {
|
|
return -1;
|
|
}
|
|
|
|
DiskIOProcessor::Flag dflags = DiskIOProcessor::Recordable;
|
|
|
|
if (_mode == Destructive && !Profile->get_trx()) {
|
|
dflags = DiskIOProcessor::Flag (dflags | DiskIOProcessor::Destructive);
|
|
} else if (_mode == NonLayered){
|
|
dflags = DiskIOProcessor::Flag(dflags | DiskIOProcessor::NonLayered);
|
|
}
|
|
|
|
_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<Route> (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<Route> (shared_from_this()));
|
|
|
|
set_align_choice_from_io ();
|
|
|
|
use_new_playlist (data_type());
|
|
|
|
boost::shared_ptr<Route> rp (boost::dynamic_pointer_cast<Route> (shared_from_this()));
|
|
boost::shared_ptr<Track> rt = boost::dynamic_pointer_cast<Track> (rp);
|
|
|
|
_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::input_changed ()
|
|
{
|
|
if (_disk_writer && _alignment_choice == Automatic) {
|
|
set_align_choice_from_io ();
|
|
}
|
|
}
|
|
|
|
XMLNode&
|
|
Track::get_state ()
|
|
{
|
|
return state (true);
|
|
}
|
|
|
|
XMLNode&
|
|
Track::state (bool full)
|
|
{
|
|
XMLNode& root (Route::state (full));
|
|
|
|
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 ("name", 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 (boost::shared_ptr<AudioPlaylist> pl = boost::dynamic_pointer_cast<AudioPlaylist> (_session.playlists->by_name (name))) {
|
|
use_playlist (DataType::AUDIO, pl);
|
|
}
|
|
|
|
if (boost::shared_ptr<MidiPlaylist> pl = boost::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;
|
|
}
|
|
|
|
XMLNode&
|
|
Track::get_template ()
|
|
{
|
|
return state (false);
|
|
}
|
|
|
|
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::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_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 ();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::resync_track_name ()
|
|
{
|
|
set_name(name());
|
|
}
|
|
|
|
bool
|
|
Track::set_name (const string& str)
|
|
{
|
|
bool ret;
|
|
|
|
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<Track> me = boost::dynamic_pointer_cast<Track> (shared_from_this ());
|
|
|
|
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.
|
|
|
|
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).
|
|
*/
|
|
_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 */
|
|
|
|
if ((ret = Route::set_name (str)) == 0) {
|
|
_session.save_state ("");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
boost::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);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Track::destructive () const
|
|
{
|
|
return _disk_writer->destructive ();
|
|
}
|
|
|
|
list<boost::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 (bool o)
|
|
{
|
|
_disk_reader->set_pending_overwrite (o);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int
|
|
Track::can_internal_playback_seek (samplecnt_t p)
|
|
{
|
|
return _disk_reader->can_internal_playback_seek (p);
|
|
}
|
|
|
|
int
|
|
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);
|
|
|
|
if (!is_private_route()) {
|
|
/* don't waste i/o cycles and butler calls
|
|
for private tracks (e.g.auditioner)
|
|
*/
|
|
_disk_reader->non_realtime_locate (p);
|
|
_disk_writer->non_realtime_locate (p);
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::non_realtime_speed_change ()
|
|
{
|
|
_disk_reader->non_realtime_speed_change ();
|
|
}
|
|
|
|
int
|
|
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);
|
|
}
|
|
|
|
int
|
|
Track::set_loop (Location* l)
|
|
{
|
|
if (_disk_reader->set_loop (l)) {
|
|
return -1;
|
|
}
|
|
return _disk_writer->set_loop (l);
|
|
}
|
|
|
|
void
|
|
Track::transport_looped (samplepos_t p)
|
|
{
|
|
return _disk_writer->transport_looped (p);
|
|
}
|
|
|
|
bool
|
|
Track::realtime_speed_change ()
|
|
{
|
|
if (_disk_reader->realtime_speed_change ()) {
|
|
return -1;
|
|
}
|
|
return _disk_writer->realtime_speed_change ();
|
|
}
|
|
|
|
void
|
|
Track::realtime_handle_transport_stopped ()
|
|
{
|
|
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 ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::transport_stopped_wallclock (struct tm & n, time_t t, bool g)
|
|
{
|
|
_disk_writer->transport_stopped_wallclock (n, t, g);
|
|
}
|
|
|
|
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)
|
|
{
|
|
boost::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, boost::shared_ptr<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) {
|
|
_playlists[dt] = p;
|
|
}
|
|
|
|
_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;
|
|
boost::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();
|
|
|
|
return use_playlist (data_type(), playlist);
|
|
}
|
|
|
|
int
|
|
Track::use_new_playlist (DataType dt)
|
|
{
|
|
string newname;
|
|
boost::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;
|
|
}
|
|
|
|
return use_playlist (dt, playlist);
|
|
}
|
|
|
|
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;
|
|
vector<string> connections;
|
|
boost::shared_ptr<Port> 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);
|
|
_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::maybe_declick (BufferSet& bufs, samplecnt_t nframes, int declick)
|
|
{
|
|
/* never declick if there is an internal generator - we just want it to
|
|
keep generating sound without interruption.
|
|
|
|
ditto if we are monitoring inputs.
|
|
*/
|
|
|
|
if (_have_internal_generator || (_monitoring_control->monitoring_choice() == MonitorInput)) {
|
|
return;
|
|
}
|
|
|
|
if (!declick) {
|
|
declick = _pending_declick;
|
|
}
|
|
|
|
if (declick != 0) {
|
|
Amp::declick (bufs, nframes, declick);
|
|
}
|
|
}
|
|
|
|
void
|
|
Track::monitoring_changed (bool, Controllable::GroupControlDisposition)
|
|
{
|
|
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
|
|
(*i)->monitoring_changed ();
|
|
}
|
|
}
|
|
|
|
MeterState
|
|
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_control->monitoring_choice() & MonitorInput) || _disk_writer->record_enabled());
|
|
} else {
|
|
// track no_roll() always metering if
|
|
rv = _meter_point == MeterInput;
|
|
}
|
|
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;
|
|
}
|
|
|
|
cerr << name() << " looking for state for track procs, DR = " << _disk_reader << endl;
|
|
|
|
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<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (srcs.front());
|
|
boost::shared_ptr<SMFSource> mfs = boost::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;
|
|
}
|
|
|
|
boost::shared_ptr<SMFSource> mfs = boost::dynamic_pointer_cast<SMFSource> (srcs.front());
|
|
boost::shared_ptr<Playlist> pl = _playlists[DataType::MIDI];
|
|
boost::shared_ptr<MidiRegion> midi_region;
|
|
CaptureInfos::const_iterator ci;
|
|
|
|
if (!mfs || !pl) {
|
|
return;
|
|
}
|
|
|
|
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::start, 0);
|
|
plist.add (Properties::length, total_capture);
|
|
plist.add (Properties::layer, 0);
|
|
|
|
boost::shared_ptr<Region> rx (RegionFactory::create (srcs, plist));
|
|
|
|
midi_region = boost::dynamic_pointer_cast<MidiRegion> (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 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;
|
|
}
|
|
|
|
BeatsSamplesConverter converter (_session.tempo_map(), capture_info.front()->start);
|
|
const samplepos_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)->samples, region_name));
|
|
|
|
|
|
// cerr << _name << ": based on ci of " << (*ci)->start << " for " << (*ci)->samples << " 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)->samples);
|
|
plist.add (Properties::length_beats, converter.from((*ci)->samples).to_double());
|
|
plist.add (Properties::name, region_name);
|
|
|
|
boost::shared_ptr<Region> rx (RegionFactory::create (srcs, plist));
|
|
midi_region = boost::dynamic_pointer_cast<MidiRegion> (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, _disk_writer->non_layered());
|
|
}
|
|
|
|
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<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (srcs.front());
|
|
boost::shared_ptr<Playlist> pl = _playlists[DataType::AUDIO];
|
|
boost::shared_ptr<AudioRegion> 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_sample());
|
|
plist.add (Properties::length, afs->length(0));
|
|
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);
|
|
|
|
region = boost::dynamic_pointer_cast<AudioRegion> (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 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, buffer_position);
|
|
plist.add (Properties::length, (*ci)->samples);
|
|
plist.add (Properties::name, region_name);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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)->samples;
|
|
}
|
|
|
|
pl->thaw ();
|
|
pl->set_capture_insertion_in_progress (false);
|
|
_session.add_command (new StatefulDiffCommand (pl));
|
|
}
|
|
|
|
|