/*
- Copyright (C) 1999-2010 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.
-
-*/
+ * Copyright (C) 1999-2019 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2006-2007 Jesse Chappell <jesse@essej.net>
+ * Copyright (C) 2006-2009 Sampo Savolainen <v2@iki.fi>
+ * Copyright (C) 2006-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2006-2016 Tim Mayberry <mojofunk@gmail.com>
+ * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com>
+ * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2013-2017 Nick Mainsbridge <mainsbridge@gmail.com>
+ * Copyright (C) 2014-2019 Ben Loftis <ben@harrisonconsoles.com>
+ * Copyright (C) 2015 GZharun <grygoriiz@wavesglobal.com>
+ * Copyright (C) 2016-2018 Len Ovens <len@ovenwerks.net>
+ *
+ * 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 <stdint.h>
#include "pbd/stacktrace.h"
#include "pbd/stl_delete.h"
#include "pbd/replace_all.h"
+#include "pbd/types_convert.h"
#include "pbd/unwind.h"
#include "ardour/amp.h"
#include "ardour/revision.h"
#include "ardour/route_graph.h"
#include "ardour/route_group.h"
+#include "ardour/rt_tasklist.h"
#include "ardour/send.h"
#include "ardour/selection.h"
#include "ardour/session.h"
#include "ardour/session_directory.h"
#include "ardour/session_playlists.h"
-#include "ardour/slave.h"
#include "ardour/smf_source.h"
-#include "ardour/slave.h"
#include "ardour/solo_isolate_control.h"
#include "ardour/source_factory.h"
#include "ardour/speakers.h"
#include "ardour/tempo.h"
#include "ardour/ticker.h"
+#include "ardour/transport_master.h"
#include "ardour/track.h"
#include "ardour/types_convert.h"
#include "ardour/user_bundle.h"
const string& snapshot_name,
BusProfile* bus_profile,
string mix_template)
- : playlists (new SessionPlaylists)
+ : _playlists (new SessionPlaylists)
, _engine (eng)
, process_function (&Session::process_with_events)
, _bounce_processing_active (false)
, _transport_sample (0)
, _seek_counter (0)
, _session_range_location (0)
- , _session_range_end_is_free (true)
- , _slave (0)
+ , _session_range_is_free (true)
, _silent (false)
, _remaining_latency_preroll (0)
+ , _engine_speed (1.0)
, _transport_speed (0)
, _default_transport_speed (1.0)
, _last_transport_speed (0)
, _signalled_varispeed (0)
, _target_transport_speed (0.0)
, auto_play_legal (false)
- , _last_slave_transport_sample (0)
- , maximum_output_latency (0)
, _requested_return_sample (-1)
, current_block_size (0)
, _worst_output_latency (0)
, _worst_input_latency (0)
- , _worst_track_latency (0)
- , _worst_track_out_latency (0)
+ , _worst_route_latency (0)
+ , _send_latency_changes (0)
, _have_captured (false)
, _non_soloed_outs_muted (false)
, _listening (false)
, _was_seamless (Config->get_seamless_loop ())
, _under_nsm_control (false)
, _xrun_count (0)
- , delta_accumulator_cnt (0)
- , average_slave_delta (1800) // !!! why 1800 ???
- , average_dir (0)
- , have_first_delta_accumulator (false)
- , _slave_state (Stopped)
- , _mtc_active (false)
- , _ltc_active (false)
+ , transport_master_tracking_state (Stopped)
+ , master_wait_end (0)
, post_export_sync (false)
, post_export_position (0)
, _exporting (false)
, _current_snapshot_name (snapshot_name)
, state_tree (0)
, state_was_pending (false)
- , _state_of_the_state (StateOfTheState(CannotSave|InitialConnecting|Loading))
+ , _state_of_the_state (StateOfTheState (CannotSave | InitialConnecting | Loading))
, _suspend_save (0)
, _save_queued (false)
, _last_roll_location (0)
, _last_roll_or_reversal_location (0)
, _last_record_location (0)
- , pending_locate_roll (false)
- , pending_locate_sample (0)
- , pending_locate_flush (false)
- , pending_abort (false)
, pending_auto_loop (false)
, _mempool ("Session", 3145728)
, lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool))
, _n_lua_scripts (0)
, _butler (new Butler (*this))
, _post_transport_work (0)
- , cumulative_rf_motion (0)
- , rf_scale (1.0)
, _locations (new Locations (*this))
, _ignore_skips_updates (false)
, _rt_thread_active (false)
, _play_range (false)
, _range_selection (-1,-1)
, _object_selection (-1,-1)
- , _preroll_record_punch_pos (-1)
, _preroll_record_trim_len (0)
, _count_in_once (false)
, main_outs (0)
store_recent_sessions (_name, _path);
bool was_dirty = dirty();
-
- _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
+ unset_dirty ();
PresentationInfo::Change.connect_same_thread (*this, boost::bind (&Session::notify_presentation_info_change, this));
StartTimeChanged.connect_same_thread (*this, boost::bind (&Session::start_time_changed, this, _1));
EndTimeChanged.connect_same_thread (*this, boost::bind (&Session::end_time_changed, this, _1));
+ Send::ChangedLatency.connect_same_thread (*this, boost::bind (&Session::send_latency_compensation_change, this));
+
emit_thread_start ();
auto_connect_thread_start ();
* session or set state for an existing one.
*/
+ _rt_tasklist.reset (new RTTaskList ());
+
if (how_many_dsp_threads () > 1) {
/* For now, only create the graph if we are using >1 DSP threads, as
it is a bit slower than the old code with 1 thread.
Analyser::flush ();
- _state_of_the_state = StateOfTheState (CannotSave|Deletion);
+ _state_of_the_state = StateOfTheState (CannotSave | Deletion);
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
ltc_tx_cleanup();
- delete _slave;
- _slave = 0;
}
/* disconnect from any and all signals that we are connected to */
/* remove I/O objects before unsetting the engine session */
_click_io.reset ();
- _ltc_input.reset ();
_ltc_output.reset ();
ControlProtocolManager::instance().drop_protocols ();
EngineStateController::instance()->remove_session();
#endif
- /* drop slave, if any. We don't use use_sync_source (0) because
- * there's no reason to do all the other stuff that may happen
- * when calling that method.
- */
- delete _slave;
-
/* deregister all ports - there will be no process or any other
* callbacks from the engine any more.
*/
Port::PortDrop (); /* EMIT SIGNAL */
+ {
+ Glib::Threads::Mutex::Lock lm (controllables_lock);
+ for (Controllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
+ (*i)->DropReferences (); /* EMIT SIGNAL */
+ }
+ controllables.clear ();
+ }
+
/* clear history so that no references to objects are held any more */
_history.clear ();
}
/* not strictly necessary, but doing it here allows the shared_ptr debugging to work */
- playlists.reset ();
+ _playlists.reset ();
emit_thread_terminate ();
bool del = true;
switch (ev->type) {
case SessionEvent::AutoLoop:
- case SessionEvent::AutoLoopDeclick:
case SessionEvent::Skip:
case SessionEvent::PunchIn:
case SessionEvent::PunchOut:
- case SessionEvent::RecordStart:
case SessionEvent::StopOnce:
case SessionEvent::RangeStop:
case SessionEvent::RangeLocate:
DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n");
+#ifndef NDEBUG
+ Controllable::dump_registry ();
+#endif
+
BOOST_SHOW_POINTERS ();
}
{
XMLNode* child = 0;
- _ltc_input.reset (new IO (*this, X_("LTC In"), IO::Input));
_ltc_output.reset (new IO (*this, X_("LTC Out"), IO::Output));
- if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC In"))) != 0) {
- _ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version);
- } else {
- {
- Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
- _ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this);
- // TODO use auto-connect thread somehow (needs a route currently)
- // see note in Session::auto_connect_thread_run() why process lock is needed.
- reconnect_ltc_input ();
- }
- }
-
if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC Out"))) != 0) {
_ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version);
} else {
* IO style of NAME/TYPE-{in,out}N
*/
- _ltc_input->nth (0)->set_name (X_("LTC-in"));
_ltc_output->nth (0)->set_name (X_("LTC-out"));
}
if (np + 1 < outputs[DataType::AUDIO].size()) {
char buf[32];
snprintf (buf, sizeof(buf), _("out %" PRIu32 "+%" PRIu32), np + 1, np + 2);
- boost::shared_ptr<Bundle> c (new Bundle (buf, true));
+ boost::shared_ptr<Bundle> c (new Bundle (buf, true));
c->add_channel (_("L"), DataType::AUDIO);
c->set_port (0, outputs[DataType::AUDIO][np]);
c->add_channel (_("R"), DataType::AUDIO);
return;
}
+ /* allow deletion when session is unloaded */
+ if (!_engine.running() && !deletion_in_progress ()) {
+ error << _("Cannot remove monitor section while the engine is offline.") << endmsg;
+ return;
+ }
+
/* force reversion to Solo-In-Place */
Config->set_solo_control_is_listen_control (false);
*/
cancel_audition ();
- {
+ if (!deletion_in_progress ()) {
/* Hold process lock while doing this so that we don't hear bits and
* pieces of audio as we work on each route.
*/
}
remove_route (_monitor_out);
- if (_state_of_the_state & Deletion) {
+ if (deletion_in_progress ()) {
return;
}
auditioner->connect ();
}
- Config->ParameterChanged ("use-monitor-bus");
+ MonitorBusAddedOrRemoved (); /* EMIT SIGNAL */
}
void
{
RouteList rl;
+ if (!_engine.running()) {
+ error << _("Cannot create monitor section while the engine is offline.") << endmsg;
+ return;
+ }
+
if (_monitor_out || !_master_out || Profile->get_trx()) {
return;
}
if (auditioner) {
auditioner->connect ();
}
- Config->ParameterChanged ("use-monitor-bus");
+
+ MonitorBusAddedOrRemoved (); /* EMIT SIGNAL */
}
void
bool
Session::record_enabling_legal () const
{
- /* this used to be in here, but survey says.... we don't need to restrict it */
- // if (record_status() == Recording) {
- // return false;
- // }
-
if (Config->get_all_safe()) {
return false;
}
{
replace_event (SessionEvent::PunchIn, location->start());
- if (get_record_enabled() && config.get_punch_in()) {
+ if (get_record_enabled() && config.get_punch_in() && !actively_recording ()) {
/* capture start has been changed, so save new pending state */
save_state ("", true);
}
void
Session::auto_punch_end_changed (Location* location)
{
- samplepos_t when_to_stop = location->end();
- // when_to_stop += _worst_output_latency + _worst_input_latency;
- replace_event (SessionEvent::PunchOut, when_to_stop);
+ replace_event (SessionEvent::PunchOut, location->end());
}
void
Session::auto_punch_changed (Location* location)
{
- samplepos_t when_to_stop = location->end();
-
- replace_event (SessionEvent::PunchIn, location->start());
- //when_to_stop += _worst_output_latency + _worst_input_latency;
- replace_event (SessionEvent::PunchOut, when_to_stop);
+ auto_punch_start_changed (location);
+ auto_punch_end_changed (location);
}
/** @param loc A loop location.
samplecnt_t dcl;
auto_loop_declick_range (location, dcp, dcl);
- if (transport_rolling() && play_loop) {
+ bool rolling = transport_rolling ();
- replace_event (SessionEvent::AutoLoopDeclick, dcp, dcl);
-
- // if (_transport_sample > location->end()) {
+ if (rolling && play_loop) {
if (_transport_sample < location->start() || _transport_sample > location->end()) {
// relocate to beginning of loop
clear_events (SessionEvent::LocateRoll);
-
request_locate (location->start(), true);
}
- else if (Config->get_seamless_loop() && !loop_changing) {
-
- // schedule a locate-roll to refill the disk readers at the
- // previous loop end
- loop_changing = true;
-
- if (location->end() > last_loopend) {
- clear_events (SessionEvent::LocateRoll);
- SessionEvent *ev = new SessionEvent (SessionEvent::LocateRoll, SessionEvent::Add, last_loopend, last_loopend, 0, true);
- queue_event (ev);
- }
-
- }
} else {
- clear_events (SessionEvent::AutoLoopDeclick);
clear_events (SessionEvent::AutoLoop);
}
samplepos_t pos;
- if (!transport_rolling() && select_playhead_priority_target (pos)) {
+ if (!rolling && select_playhead_priority_target (pos)) {
if (pos == location->start()) {
request_locate (pos);
}
}
-
last_loopend = location->end();
set_dirty ();
}
if ((existing = _locations->auto_punch_location()) != 0 && existing != location) {
punch_connections.drop_connections();
existing->set_auto_punch (false, this);
- remove_event (existing->start(), SessionEvent::PunchIn);
+ clear_events (SessionEvent::PunchIn);
clear_events (SessionEvent::PunchOut);
auto_punch_location_changed (0);
}
samplepos_t dcp;
samplecnt_t dcl;
auto_loop_declick_range (existing, dcp, dcl);
- remove_event (dcp, SessionEvent::AutoLoopDeclick);
auto_loop_location_changed (0);
}
Locations::LocationList skips;
- if (consolidate) {
- PBD::Unwinder<bool> uw (_ignore_skips_updates, true);
- consolidate_skips (loc);
- }
+ if (consolidate) {
+ PBD::Unwinder<bool> uw (_ignore_skips_updates, true);
+ consolidate_skips (loc);
+ }
sync_locations_to_skips ();
void
Session::consolidate_skips (Location* loc)
{
- Locations::LocationList all_locations = _locations->list ();
+ Locations::LocationList all_locations = _locations->list ();
- for (Locations::LocationList::iterator l = all_locations.begin(); l != all_locations.end(); ) {
+ for (Locations::LocationList::iterator l = all_locations.begin(); l != all_locations.end(); ) {
- if (!(*l)->is_skip ()) {
- ++l;
- continue;
- }
+ if (!(*l)->is_skip ()) {
+ ++l;
+ continue;
+ }
- /* don't test against self */
+ /* don't test against self */
- if (*l == loc) {
- ++l;
- continue;
- }
+ if (*l == loc) {
+ ++l;
+ continue;
+ }
- switch (Evoral::coverage ((*l)->start(), (*l)->end(), loc->start(), loc->end())) {
- case Evoral::OverlapInternal:
- case Evoral::OverlapExternal:
- case Evoral::OverlapStart:
- case Evoral::OverlapEnd:
- /* adjust new location to cover existing one */
- loc->set_start (min (loc->start(), (*l)->start()));
- loc->set_end (max (loc->end(), (*l)->end()));
- /* we don't need this one any more */
- _locations->remove (*l);
- /* the location has been deleted, so remove reference to it in our local list */
- l = all_locations.erase (l);
- break;
+ switch (Evoral::coverage ((*l)->start(), (*l)->end(), loc->start(), loc->end())) {
+ case Evoral::OverlapInternal:
+ case Evoral::OverlapExternal:
+ case Evoral::OverlapStart:
+ case Evoral::OverlapEnd:
+ /* adjust new location to cover existing one */
+ loc->set_start (min (loc->start(), (*l)->start()));
+ loc->set_end (max (loc->end(), (*l)->end()));
+ /* we don't need this one any more */
+ _locations->remove (*l);
+ /* the location has been deleted, so remove reference to it in our local list */
+ l = all_locations.erase (l);
+ break;
- case Evoral::OverlapNone:
- ++l;
- break;
- }
- }
+ case Evoral::OverlapNone:
+ ++l;
+ break;
+ }
+ }
}
void
void
Session::location_added (Location *location)
{
- if (location->is_auto_punch()) {
- set_auto_punch_location (location);
- }
+ if (location->is_auto_punch()) {
+ set_auto_punch_location (location);
+ }
- if (location->is_auto_loop()) {
- set_auto_loop_location (location);
- }
+ if (location->is_auto_loop()) {
+ set_auto_loop_location (location);
+ }
- if (location->is_session_range()) {
- /* no need for any signal handling or event setting with the session range,
- because we keep a direct reference to it and use its start/end directly.
- */
- _session_range_location = location;
- }
+ if (location->is_session_range()) {
+ /* no need for any signal handling or event setting with the session range,
+ because we keep a direct reference to it and use its start/end directly.
+ */
+ _session_range_location = location;
+ }
- if (location->is_mark()) {
- /* listen for per-location signals that require us to do any * global updates for marks */
+ if (location->is_mark()) {
+ /* listen for per-location signals that require us to do any * global updates for marks */
- location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
- location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
- location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
- location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
location->PositionLockStyleChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
- }
+ }
if (location->is_range_marker()) {
- /* listen for per-location signals that require us to do any * global updates for marks */
+ /* listen for per-location signals that require us to do any * global updates for marks */
- location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
- location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
- location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
- location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
+ location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
location->PositionLockStyleChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
- }
+ }
- if (location->is_skip()) {
- /* listen for per-location signals that require us to update skip-locate events */
+ if (location->is_skip()) {
+ /* listen for per-location signals that require us to update skip-locate events */
- location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
- location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
- location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
- location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, false));
+ location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
+ location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
+ location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true));
+ location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, false));
location->PositionLockStyleChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
- update_skips (location, true);
- }
+ update_skips (location, true);
+ }
set_dirty ();
}
void
Session::location_removed (Location *location)
{
- if (location->is_auto_loop()) {
- set_auto_loop_location (0);
- set_track_loop (false);
- }
+ if (location->is_auto_loop()) {
+ set_auto_loop_location (0);
+ if (!play_loop) {
+ set_track_loop (false);
+ }
+ unset_play_loop ();
+ }
- if (location->is_auto_punch()) {
- set_auto_punch_location (0);
- }
+ if (location->is_auto_punch()) {
+ set_auto_punch_location (0);
+ }
- if (location->is_session_range()) {
- /* this is never supposed to happen */
- error << _("programming error: session range removed!") << endl;
- }
+ if (location->is_session_range()) {
+ /* this is never supposed to happen */
+ error << _("programming error: session range removed!") << endl;
+ }
- if (location->is_skip()) {
+ if (location->is_skip()) {
- update_skips (location, false);
- }
+ update_skips (location, false);
+ }
set_dirty ();
}
void
Session::locations_changed ()
{
- _locations->apply (*this, &Session::_locations_changed);
+ _locations->apply (*this, &Session::_locations_changed);
}
void
Session::_locations_changed (const Locations::LocationList& locations)
{
- /* There was some mass-change in the Locations object.
-
- We might be re-adding a location here but it doesn't actually matter
- for all the locations that the Session takes an interest in.
- */
+ /* There was some mass-change in the Locations object.
+ *
+ * We might be re-adding a location here but it doesn't actually matter
+ * for all the locations that the Session takes an interest in.
+ */
{
PBD::Unwinder<bool> protect_ignore_skip_updates (_ignore_skips_updates, true);
}
RecordStateChanged (); /* emit signal */
-
- if (!rt_context) {
- remove_pending_capture_state ();
- }
- unset_preroll_record_punch ();
}
}
}
if (_transport_speed) {
- if (!config.get_punch_in() && !preroll_record_punch_enabled ()) {
+ if (!config.get_punch_in()) {
enable_record ();
}
} else {
static void
-trace_terminal (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> rbase)
+trace_terminal (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> rbase, bool sends_only)
{
boost::shared_ptr<Route> r2;
base as being fed by r2
*/
- rbase->add_fed_by (r2, i->sends_only);
+ rbase->add_fed_by (r2, i->sends_only || sends_only);
if (r2 != rbase) {
all routes that feed r2
*/
- trace_terminal (r2, rbase);
+ trace_terminal (r2, rbase, i->sends_only || sends_only);
}
}
are being destroyed.
*/
- if (_state_of_the_state & (InitialConnecting | Deletion)) {
+ if (inital_connect_or_deletion_in_progress ()) {
return;
}
or indirectly feeds them.
*/
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- trace_terminal (*i, *i);
+ trace_terminal (*i, *i, false);
}
*r = *sorted_routes;
--how_many;
}
- failed:
+ failed:
if (!new_routes.empty()) {
StateProtector sp (this);
if (Profile->get_trx()) {
--how_many;
}
- failure:
+ failure:
if (!ret.empty()) {
StateProtector sp (this);
add_routes (ret, false, false, false, order);
void
Session::reconnect_midi_scene_ports(bool inputs)
{
- if (inputs ) {
-
- boost::shared_ptr<MidiPort> scene_in_ptr = scene_in();
- if (scene_in_ptr) {
- scene_in_ptr->disconnect_all ();
-
- std::vector<EngineStateController::MidiPortState> midi_port_states;
- EngineStateController::instance()->get_physical_midi_input_states (midi_port_states);
-
- std::vector<EngineStateController::MidiPortState>::iterator state_iter = midi_port_states.begin();
-
- for (; state_iter != midi_port_states.end(); ++state_iter) {
- if (state_iter->active && state_iter->available && state_iter->scene_connected) {
- scene_in_ptr->connect (state_iter->name);
- }
- }
- }
+ if (inputs ) {
- } else {
+ boost::shared_ptr<MidiPort> scene_in_ptr = scene_in();
+ if (scene_in_ptr) {
+ scene_in_ptr->disconnect_all ();
- boost::shared_ptr<MidiPort> scene_out_ptr = scene_out();
+ std::vector<EngineStateController::MidiPortState> midi_port_states;
+ EngineStateController::instance()->get_physical_midi_input_states (midi_port_states);
- if (scene_out_ptr ) {
- scene_out_ptr->disconnect_all ();
+ std::vector<EngineStateController::MidiPortState>::iterator state_iter = midi_port_states.begin();
- std::vector<EngineStateController::MidiPortState> midi_port_states;
- EngineStateController::instance()->get_physical_midi_output_states (midi_port_states);
-
- std::vector<EngineStateController::MidiPortState>::iterator state_iter = midi_port_states.begin();
+ for (; state_iter != midi_port_states.end(); ++state_iter) {
+ if (state_iter->active && state_iter->available && state_iter->scene_connected) {
+ scene_in_ptr->connect (state_iter->name);
+ }
+ }
+ }
- for (; state_iter != midi_port_states.end(); ++state_iter) {
- if (state_iter->active && state_iter->available && state_iter->scene_connected) {
- scene_out_ptr->connect (state_iter->name);
- }
- }
- }
- }
-}
+ } else {
-void
-Session::reconnect_mtc_ports ()
-{
- boost::shared_ptr<MidiPort> mtc_in_ptr = _midi_ports->mtc_input_port();
+ boost::shared_ptr<MidiPort> scene_out_ptr = scene_out();
- if (!mtc_in_ptr) {
- return;
- }
+ if (scene_out_ptr ) {
+ scene_out_ptr->disconnect_all ();
- mtc_in_ptr->disconnect_all ();
+ std::vector<EngineStateController::MidiPortState> midi_port_states;
+ EngineStateController::instance()->get_physical_midi_output_states (midi_port_states);
- std::vector<EngineStateController::MidiPortState> midi_port_states;
- EngineStateController::instance()->get_physical_midi_input_states (midi_port_states);
+ std::vector<EngineStateController::MidiPortState>::iterator state_iter = midi_port_states.begin();
- std::vector<EngineStateController::MidiPortState>::iterator state_iter = midi_port_states.begin();
-
- for (; state_iter != midi_port_states.end(); ++state_iter) {
- if (state_iter->available && state_iter->mtc_in) {
- mtc_in_ptr->connect (state_iter->name);
+ for (; state_iter != midi_port_states.end(); ++state_iter) {
+ if (state_iter->active && state_iter->available && state_iter->scene_connected) {
+ scene_out_ptr->connect (state_iter->name);
+ }
+ }
}
}
-
- if (!_midi_ports->mtc_input_port ()->connected () &&
- config.get_external_sync () &&
- (Config->get_sync_source () == MTC) ) {
- config.set_external_sync (false);
- }
-
- if ( ARDOUR::Profile->get_trx () ) {
- // Tracks need this signal to update timecode_source_dropdown
- MtcOrLtcInputPortChanged (); //emit signal
- }
}
void
--how_many;
}
- failed:
+ failed:
if (!new_routes.empty()) {
StateProtector sp (this);
if (Profile->get_trx()) {
--how_many;
}
- failure:
+ failure:
if (!ret.empty()) {
StateProtector sp (this);
if (Profile->get_trx()) {
add_routes (ret, false, false, false, order);
+ } else if (flags == PresentationInfo::FoldbackBus) {
+ add_routes (ret, false, false, true, order); // no autoconnect
} else {
add_routes (ret, false, true, true, order); // autoconnect // outputs only
}
*/
XMLNode node_copy (node);
+ std::vector<boost::shared_ptr<Playlist> > shared_playlists;
try {
string name;
if (!find_route_name (name_base.c_str(), ++number, name, (being_added > 1))) {
fatal << _("Session: UINT_MAX routes? impossible!") << endmsg;
- /*NOTREACHDE*/
+ /*NOTREACHED*/
}
} else {
}
}
- /* set this name in the XML description that we are about to use */
+ /* figure out the appropriate playlist setup. The track
+ * (if the Route we're creating is a track) will find
+ * playlists via ID.
+ */
if (pd == CopyPlaylist) {
- XMLNode* ds_node = find_named_node (node_copy, "Diskstream");
- if (ds_node) {
- const std::string playlist_name = ds_node->property (X_("playlist"))->value ();
- boost::shared_ptr<Playlist> playlist = playlists->by_name (playlist_name);
- // Use same name as Route::set_name_in_state so playlist copy
- // is picked up when creating the Route in XMLRouteFactory below
+
+ PBD::ID playlist_id;
+
+ if (node_copy.get_property (X_("audio-playlist"), playlist_id)) {
+ boost::shared_ptr<Playlist> playlist = _playlists->by_id (playlist_id);
+ playlist = PlaylistFactory::create (playlist, string_compose ("%1.1", name));
+ playlist->reset_shares ();
+ node_copy.set_property (X_("audio-playlist"), playlist->id());
+ }
+
+ if (node_copy.get_property (X_("midi-playlist"), playlist_id)) {
+ boost::shared_ptr<Playlist> playlist = _playlists->by_id (playlist_id);
playlist = PlaylistFactory::create (playlist, string_compose ("%1.1", name));
playlist->reset_shares ();
+ node_copy.set_property (X_("midi-playlist"), playlist->id());
}
+
} else if (pd == SharePlaylist) {
- XMLNode* ds_node = find_named_node (node_copy, "Diskstream");
- if (ds_node) {
- const std::string playlist_name = ds_node->property (X_("playlist"))->value ();
- boost::shared_ptr<Playlist> playlist = playlists->by_name (playlist_name);
- playlist->share_with ((node_copy.property (X_("id")))->value());
+ PBD::ID playlist_id;
+
+ if (node_copy.get_property (X_("audio-playlist"), playlist_id)) {
+ boost::shared_ptr<Playlist> playlist = _playlists->by_id (playlist_id);
+ shared_playlists.push_back (playlist);
+ }
+
+ if (node_copy.get_property (X_("midi-playlist"), playlist_id)) {
+ boost::shared_ptr<Playlist> playlist = _playlists->by_id (playlist_id);
+ shared_playlists.push_back (playlist);
+ }
+
+ } else { /* NewPlaylist */
+
+ PBD::ID pid;
+
+ if (node_copy.get_property (X_("audio-playlist"), pid)) {
+ boost::shared_ptr<Playlist> playlist = PlaylistFactory::create (DataType::AUDIO, *this, name, false);
+ node_copy.set_property (X_("audio-playlist"), playlist->id());
+ }
+
+ if (node_copy.get_property (X_("midi-playlist"), pid)) {
+ boost::shared_ptr<Playlist> playlist = PlaylistFactory::create (DataType::MIDI, *this, name, false);
+ node_copy.set_property (X_("midi-playlist"), playlist->id());
}
}
- bool rename_playlist = (pd == CopyPlaylist || pd == NewPlaylist);
+ /* Fix up new name in the XML node */
- Route::set_name_in_state (node_copy, name, rename_playlist);
+ Route::set_name_in_state (node_copy, name);
/* trim bitslots from listen sends so that new ones are used */
XMLNodeList children = node_copy.children ();
(*i)->remove_property (X_("name"));
(*i)->set_property ("bitslot", bitslot);
(*i)->set_property ("name", name);
+ XMLNodeList io_kids = (*i)->children ();
+ for (XMLNodeList::iterator j = io_kids.begin(); j != io_kids.end(); ++j) {
+ if ((*j)->name() != X_("IO")) {
+ continue;
+ }
+ (*j)->remove_property (X_("name"));
+ (*j)->set_property ("name", name);
+ }
}
else if (type && type->value() == X_("intreturn")) {
(*i)->remove_property (X_("bitslot"));
(*i)->remove_property (X_("bitslot"));
}
else if (type && type->value() == X_("port")) {
- // PortInsert::set_state() handles the bitslot
- (*i)->remove_property (X_("bitslot"));
- (*i)->set_property ("ignore-name", "1");
+ IOProcessor::prepare_for_reset (**i, name);
}
}
}
/* new routes start off unsoloed to avoid issues related to
- upstream / downstream buses. */
+ upstream / downstream buses.
+ */
node_copy.remove_node_and_delete (X_("Controllable"), X_("name"), X_("solo"));
boost::shared_ptr<Route> route (XMLRouteFactory (node_copy, 3000));
goto out;
}
+ /* Fix up sharing of playlists with the new Route/Track */
+
+ for (vector<boost::shared_ptr<Playlist> >::iterator sp = shared_playlists.begin(); sp != shared_playlists.end(); ++sp) {
+ (*sp)->share_with (route->id());
+ }
+
if (boost::dynamic_pointer_cast<Track>(route)) {
/* force input/output change signals so that the new diskstream
picks up the configuration of the route. During session
goto out;
}
+ catch (...) {
+ IO::enable_connecting ();
+ throw;
+ }
+
--how_many;
}
- out:
+ out:
if (!ret.empty()) {
StateProtector sp (this);
if (Profile->get_trx()) {
} else {
add_routes (ret, true, true, false, insert_at);
}
- IO::enable_connecting ();
}
+ IO::enable_connecting ();
+
return ret;
}
}
/* speed up session deletion, don't do the solo dance */
- if (0 == (_state_of_the_state & Deletion)) {
+ if (!deletion_in_progress ()) {
(*iter)->solo_control()->set_value (0.0, Controllable::NoGroup);
}
(*iter)->output()->disconnect (0);
/* if the route had internal sends sending to it, remove them */
- if ((*iter)->internal_return()) {
+
+ if (!deletion_in_progress () && (*iter)->internal_return()) {
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
resort_routes ();
#endif
- if (_process_graph && !(_state_of_the_state & Deletion)) {
+ if (_process_graph && !deletion_in_progress() && _engine.running()) {
_process_graph->clear_other_chain ();
}
}
in_signal_flow = true;
} else {
- DEBUG_TRACE (DEBUG::Solo, "\tno feed to\n");
+ DEBUG_TRACE (DEBUG::Solo, string_compose("\tno feed to %1\n", (*i)->name()) );
}
if (!in_signal_flow) {
}
void
-Session::get_stripables (StripableList& sl) const
+Session::get_stripables (StripableList& sl, PresentationInfo::Flag fl) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
- sl.insert (sl.end(), r->begin(), r->end());
+ for (RouteList::iterator it = r->begin(); it != r->end(); ++it) {
+ if ((*it)->presentation_info ().flags () & fl) {
+ sl.push_back (*it);
+ }
+ }
- VCAList v = _vca_manager->vcas ();
- sl.insert (sl.end(), v.begin(), v.end());
+ if (fl & PresentationInfo::VCA) {
+ VCAList v = _vca_manager->vcas ();
+ sl.insert (sl.end(), v.begin(), v.end());
+ }
}
StripableList
Session::get_stripables () const
{
+ PresentationInfo::Flag fl = PresentationInfo::AllStripables;
StripableList rv;
- Session::get_stripables (rv);
+ Session::get_stripables (rv, fl);
rv.sort (Stripable::Sorter ());
return rv;
}
+RouteList
+Session::get_routelist (bool mixer_order, PresentationInfo::Flag fl) const
+{
+ boost::shared_ptr<RouteList> r = routes.reader ();
+ RouteList rv;
+ for (RouteList::iterator it = r->begin(); it != r->end(); ++it) {
+ if ((*it)->presentation_info ().flags () & fl) {
+ rv.push_back (*it);
+ }
+ }
+ rv.sort (Stripable::Sorter (mixer_order));
+ return rv;
+}
+
boost::shared_ptr<RouteList>
Session::get_routes_with_internal_returns() const
{
void
Session::maybe_update_session_range (samplepos_t a, samplepos_t b)
{
- if (_state_of_the_state & Loading) {
+ if (loading ()) {
return;
}
} else {
- if (a < _session_range_location->start()) {
+ if (_session_range_is_free && (a < _session_range_location->start())) {
_session_range_location->set_start (a);
}
- if (_session_range_end_is_free && (b > _session_range_location->end())) {
+ if (_session_range_is_free && (b > _session_range_location->end())) {
_session_range_location->set_end (b);
}
}
}
void
-Session::set_end_is_free (bool yn)
+Session::set_session_range_is_free (bool yn)
{
- _session_range_end_is_free = yn;
+ _session_range_is_free = yn;
}
void
tmp = r;
++tmp;
- playlists->destroy_region (*r);
+ _playlists->destroy_region (*r);
RegionFactory::map_remove (*r);
(*r)->drop_sources ();
(*s)->mark_for_remove ();
(*s)->drop_references ();
+ SourceRemoved(*s);
s = srcs.erase (s);
}
return 0;
}
+void
+Session::get_last_capture_sources (std::list<boost::shared_ptr<Source> >& srcs)
+{
+ boost::shared_ptr<RouteList> rl = routes.reader ();
+ for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+ if (!tr) {
+ continue;
+ }
+
+ list<boost::shared_ptr<Source> >& l = tr->last_capture_sources();
+
+ if (!l.empty()) {
+ srcs.insert (srcs.end(), l.begin(), l.end());
+ l.clear ();
+ }
+ }
+}
+
/* Source Management */
void
}
source->DropReferences.connect_same_thread (*this, boost::bind (&Session::remove_source, this, boost::weak_ptr<Source> (source)));
+
+ SourceAdded(source);
}
}
void
Session::remove_source (boost::weak_ptr<Source> src)
{
- if (_state_of_the_state & Deletion) {
+ if (deletion_in_progress ()) {
return;
}
if ((i = sources.find (source->id())) != sources.end()) {
sources.erase (i);
+ SourceRemoved(source);
}
}
- if (!(_state_of_the_state & StateOfTheState (InCleanup|Loading))) {
+ if (!in_cleanup () && !loading ()) {
/* save state so we don't end up with a session file
- referring to non-existent sources.
- */
+ * referring to non-existent sources.
+ */
save_state (_current_snapshot_name);
}
DataType::MIDI, *this, path, false, sample_rate()));
}
+bool
+Session::playlist_is_active (boost::shared_ptr<Playlist> playlist)
+{
+ Glib::Threads::Mutex::Lock lm (_playlists->lock);
+ for (SessionPlaylists::List::iterator i = _playlists->playlists.begin(); i != _playlists->playlists.end(); i++) {
+ if ( (*i) == playlist ) {
+ return true;
+ }
+ }
+ return false;
+}
void
Session::add_playlist (boost::shared_ptr<Playlist> playlist, bool unused)
return;
}
- playlists->add (playlist);
+ _playlists->add (playlist);
if (unused) {
playlist->release();
void
Session::remove_playlist (boost::weak_ptr<Playlist> weak_playlist)
{
- if (_state_of_the_state & Deletion) {
+ if (deletion_in_progress ()) {
return;
}
return;
}
- playlists->remove (playlist);
+ _playlists->remove (playlist);
set_dirty();
}
#ifndef NDEBUG
lua.Print.connect (&_lua_print);
#endif
- lua.tweak_rt_gc ();
lua.sandbox (true);
lua.do_command (
"function ArdourSession ()"
abort(); /*NOTREACHED*/
}
+ lua_mlock (L, 1);
LuaBindings::stddef (L);
LuaBindings::common (L);
LuaBindings::dsp (L);
+ lua_mlock (L, 0);
luabridge::push <Session *> (L, this);
lua_setglobal (L, "Session");
}
from a set_state() call or creating new tracks. Ditto for deletion.
*/
- if ((_state_of_the_state & (InitialConnecting|Deletion)) || _adding_routes_in_progress || _reconnecting_routes_in_progress || _route_deletion_in_progress) {
+ if (inital_connect_or_deletion_in_progress () || _adding_routes_in_progress || _reconnecting_routes_in_progress || _route_deletion_in_progress) {
return;
}
resort_routes ();
/* force all diskstreams to update their capture offset values to
- reflect any changes in latencies within the graph.
- */
-
- boost::shared_ptr<RouteList> rl = routes.reader ();
- for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
- (*i)->update_signal_latency (true); // XXX
- }
+ * reflect any changes in latencies within the graph.
+ */
+ update_route_latency (false, true);
}
/** @return Number of samples that there is disk space available to write,
{
clear_clicks ();
- playlists->update_after_tempo_map_change ();
+ _playlists->update_after_tempo_map_change ();
_locations->apply (*this, &Session::update_locations_after_tempo_map_change);
void
Session::unmark_return_id (uint32_t id)
{
- if (_state_of_the_state & Deletion) { return; }
+ if (deletion_in_progress ()) {
+ return;
+ }
if (id < return_bitset.size()) {
return_bitset[id] = false;
}
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr) {
- /* don't save state as we do this, there's no point
- */
- _state_of_the_state = StateOfTheState (_state_of_the_state|InCleanup);
+ /* don't save state as we do this, there's no point */
+ _state_of_the_state = StateOfTheState (_state_of_the_state | InCleanup);
tr->reset_write_sources (false);
_state_of_the_state = StateOfTheState (_state_of_the_state & ~InCleanup);
}
string legal_playlist_name;
string possible_path;
+ DataType data_type = track.data_type();
+
if (end <= start) {
error << string_compose (_("Cannot write a range where end <= start (e.g. %1 <= %2)"),
end, start) << endmsg;
diskstream_channels = track.bounce_get_output_streams (diskstream_channels, endpoint,
include_endpoint, for_export, for_freeze);
- if (diskstream_channels.n(track.data_type()) < 1) {
+ if (data_type == DataType::MIDI && endpoint && !for_export && !for_freeze && diskstream_channels.n(DataType::AUDIO) > 0) {
+ data_type = DataType::AUDIO;
+ }
+
+ if (diskstream_channels.n(data_type) < 1) {
error << _("Cannot write a range with no data.") << endmsg;
return result;
}
goto out;
}
- legal_playlist_name = legalize_for_path (playlist->name());
+ legal_playlist_name = "(bounce)" + legalize_for_path (playlist->name());
- for (uint32_t chan_n = 0; chan_n < diskstream_channels.n(track.data_type()); ++chan_n) {
+ for (uint32_t chan_n = 0; chan_n < diskstream_channels.n(data_type); ++chan_n) {
string base_name = string_compose ("%1-%2-bounce", playlist->name(), chan_n);
- string path = ((track.data_type() == DataType::AUDIO)
+ string path = ((data_type == DataType::AUDIO)
? new_audio_source_path (legal_playlist_name, diskstream_channels.n_audio(), chan_n, false, true)
: new_midi_source_path (legal_playlist_name));
}
try {
- source = SourceFactory::createWritable (track.data_type(), *this, path, false, sample_rate());
+ source = SourceFactory::createWritable (data_type, *this, path, false, sample_rate());
}
catch (failed_constructor& err) {
const MidiBuffer& buf = buffers.get_midi(0);
for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
Evoral::Event<samplepos_t> ev = *i;
- ev.set_time(ev.time() - position);
- ms->append_event_samples(lock, ev, ms->timeline_position());
+ if (!endpoint || for_export) {
+ ev.set_time(ev.time() - position);
+ }
+ ms->append_event_samples(lock, ev, ms->natural_position());
}
}
}
PropertyList plist;
plist.add (Properties::start, 0);
- plist.add (Properties::length, srcs.front()->length(srcs.front()->timeline_position()));
+ plist.add (Properties::whole_file, true);
+ plist.add (Properties::length, srcs.front()->length(srcs.front()->natural_position()));
plist.add (Properties::name, region_name_from_path (srcs.front()->name(), true));
- result = RegionFactory::create (srcs, plist);
+ result = RegionFactory::create (srcs, plist, true);
}
- out:
+ out:
if (!result) {
for (vector<boost::shared_ptr<Source> >::iterator src = srcs.begin(); src != srcs.end(); ++src) {
(*src)->mark_for_remove ();
return rv;
}
+bool
+Session::plot_process_graph (std::string const& file_name) const {
+ return _process_graph ? _process_graph->plot (file_name) : false;
+}
+
void
Session::add_automation_list(AutomationList *al)
{
bool
Session::have_rec_disabled_track () const
{
- return g_atomic_int_get (const_cast<gint*>(&_have_rec_disabled_track)) == 1;
+ return g_atomic_int_get (const_cast<gint*>(&_have_rec_disabled_track)) == 1;
}
/** Update the state of our rec-enabled tracks flag */
void
Session::listen_position_changed ()
{
+ if (loading ()) {
+ /* skip duing session restore (already taken care of) */
+ return;
+ }
ProcessorChangeBlocker pcb (this);
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
}
}
+boost::shared_ptr<AudioTrack>
+Session::get_nth_audio_track (int nth) const
+{
+ boost::shared_ptr<RouteList> rl = routes.reader ();
+ rl->sort (Stripable::Sorter ());
+
+ for (RouteList::const_iterator r = rl->begin(); r != rl->end(); ++r) {
+ if (!boost::dynamic_pointer_cast<AudioTrack> (*r)) {
+ continue;
+ }
+
+ if (--nth > 0) {
+ continue;
+ }
+ return boost::dynamic_pointer_cast<AudioTrack> (*r);
+ }
+ return boost::shared_ptr<AudioTrack> ();
+}
+
boost::shared_ptr<RouteList>
Session::get_tracks () const
{
}
void
-Session::update_latency (bool playback)
+Session::set_worst_io_latencies_x (IOChange, void *)
{
+ set_worst_io_latencies ();
+}
+void
+Session::send_latency_compensation_change ()
+{
+ /* As a result of Send::set_output_latency()
+ * or InternalReturn::set_playback_offset ()
+ * the send's own latency can change (source track
+ * is aligned with target bus).
+ *
+ * This can only happen be triggered by
+ * Route::update_signal_latency ()
+ * when updating the processor latency.
+ *
+ * We need to walk the graph again to take those changes into account
+ * (we should probably recurse or process the graph in a 2 step process).
+ */
+ ++_send_latency_changes;
+}
+
+bool
+Session::update_route_latency (bool playback, bool apply_to_delayline)
+{
+ /* Note: RouteList is process-graph sorted */
+ boost::shared_ptr<RouteList> r = routes.reader ();
+
+ if (playback) {
+ /* reverse the list so that we work backwards from the last route to run to the first,
+ * this is not needed, but can help to reduce the iterations for aux-sends.
+ */
+ RouteList* rl = routes.reader().get();
+ r.reset (new RouteList (*rl));
+ reverse (r->begin(), r->end());
+ }
+
+ bool changed = false;
+ int bailout = 0;
+restart:
+ _send_latency_changes = 0;
+ _worst_route_latency = 0;
+
+ for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+ // if (!(*i)->active()) { continue ; } // TODO
+ samplecnt_t l;
+ if ((*i)->signal_latency () != (l = (*i)->update_signal_latency (apply_to_delayline))) {
+ changed = true;
+ }
+ _worst_route_latency = std::max (l, _worst_route_latency);
+ }
+
+ if (_send_latency_changes > 0) {
+ // only 1 extra iteration is needed (we allow only 1 level of aux-sends)
+ // BUT.. jack'n'sends'n'bugs
+ if (++bailout < 5) {
+ cerr << "restarting Session::update_latency. # of send changes: " << _send_latency_changes << " iteration: " << bailout << endl;
+ goto restart;
+ }
+ }
+
+ DEBUG_TRACE (DEBUG::Latency, string_compose ("worst signal processing latency: %1 (changed ? %2)\n", _worst_route_latency, (changed ? "yes" : "no")));
+
+ return changed;
+}
+
+void
+Session::update_latency (bool playback)
+{
DEBUG_TRACE (DEBUG::Latency, string_compose ("JACK latency callback: %1\n", (playback ? "PLAYBACK" : "CAPTURE")));
- if ((_state_of_the_state & (InitialConnecting|Deletion)) || _adding_routes_in_progress || _route_deletion_in_progress) {
+ if (inital_connect_or_deletion_in_progress () || _adding_routes_in_progress || _route_deletion_in_progress) {
return;
}
if (!_engine.running()) {
}
if (playback) {
- post_playback_latency ();
+ set_worst_output_latency ();
+ update_route_latency (true, true);
} else {
- post_capture_latency ();
+ set_worst_input_latency ();
+ update_route_latency (false, false);
}
DEBUG_TRACE (DEBUG::Latency, "JACK latency callback: DONE\n");
}
-void
-Session::post_playback_latency ()
-{
- set_worst_playback_latency ();
-
- boost::shared_ptr<RouteList> r = routes.reader ();
-
- _worst_track_out_latency = 0; // XXX remove me
-
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- assert (!(*i)->is_auditioner()); // XXX remove me
- _worst_track_latency = max (_worst_track_latency, (*i)->update_signal_latency ());
- }
-
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- if (!(*i)->active()) { continue ; }
- (*i)->apply_latency_compensation ();
- }
-}
-
-void
-Session::post_capture_latency ()
-{
- set_worst_capture_latency ();
-
- /* reflect any changes in capture latencies into capture offsets */
-
- boost::shared_ptr<RouteList> rl = routes.reader();
- for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
- (*i)->update_signal_latency ();
- }
-}
-
void
Session::initialize_latencies ()
{
void
Session::set_worst_io_latencies ()
{
- set_worst_playback_latency ();
- set_worst_capture_latency ();
+ set_worst_output_latency ();
+ set_worst_input_latency ();
}
void
-Session::set_worst_playback_latency ()
+Session::set_worst_output_latency ()
{
- if (_state_of_the_state & (InitialConnecting|Deletion)) {
+ if (inital_connect_or_deletion_in_progress ()) {
return;
}
_worst_output_latency = 0;
- if (!_engine.connected()) {
+ if (!_engine.running()) {
return;
}
}
void
-Session::set_worst_capture_latency ()
+Session::set_worst_input_latency ()
{
- if (_state_of_the_state & (InitialConnecting|Deletion)) {
+ if (inital_connect_or_deletion_in_progress ()) {
return;
}
_worst_input_latency = 0;
- if (!_engine.connected()) {
+ if (!_engine.running()) {
return;
}
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (!tr) {
- continue;
- }
_worst_input_latency = max (_worst_input_latency, (*i)->input()->latency());
}
- DEBUG_TRACE (DEBUG::Latency, string_compose ("Worst input latency: %1\n", _worst_input_latency));
+ DEBUG_TRACE (DEBUG::Latency, string_compose ("Worst input latency: %1\n", _worst_input_latency));
}
void
Session::update_latency_compensation (bool force_whole_graph)
{
- // TODO: consolidate
- bool some_track_latency_changed = false;
-
- if (_state_of_the_state & (InitialConnecting|Deletion)) {
+ if (inital_connect_or_deletion_in_progress ()) {
return;
}
-
- DEBUG_TRACE(DEBUG::Latency, "---------------------------- update latency compensation\n\n");
-
- _worst_track_latency = 0;
-
- boost::shared_ptr<RouteList> r = routes.reader ();
-
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- assert (!(*i)->is_auditioner()); // XXX remove me
- if ((*i)->active()) {
- samplecnt_t tl;
- if ((*i)->signal_latency () != (tl = (*i)->update_signal_latency () /* - (*i)->output()->user_latency()*/)) {
- some_track_latency_changed = true;
- }
- _worst_track_latency = max (tl, _worst_track_latency);
- }
+ /* this lock is not usually contended, but under certain conditions,
+ * update_latency_compensation may be called concurrently.
+ * e.g. drag/drop copy a latent plugin while rolling.
+ * GUI thread (via route_processors_changed) and
+ * auto_connect_thread_run may race.
+ */
+ Glib::Threads::Mutex::Lock lx (_update_latency_lock, Glib::Threads::TRY_LOCK);
+ if (!lx.locked()) {
+ /* no need to do this twice */
+ return;
}
- DEBUG_TRACE (DEBUG::Latency, string_compose ("worst signal processing latency: %1 (changed ? %2)\n", _worst_track_latency,
- (some_track_latency_changed ? "yes" : "no")));
-
- DEBUG_TRACE(DEBUG::Latency, "---------------------------- DONE update latency compensation\n\n");
+ bool some_track_latency_changed = update_route_latency (false, false);
if (some_track_latency_changed || force_whole_graph) {
_engine.update_latencies ();
- }
-
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- (*i)->update_signal_latency (true);
+ /* above call will ask the backend up update its latencies, which
+ * eventually will trigger AudioEngine::latency_callback () and
+ * call Session::update_latency ()
+ */
+ } else {
+ boost::shared_ptr<RouteList> r = routes.reader ();
+ for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+ (*i)->apply_latency_compensation ();
+ }
}
}
return (find (_current_trans_quarks.begin(), _current_trans_quarks.end(), op) != _current_trans_quarks.end());
}
-boost::shared_ptr<Port>
-Session::ltc_input_port () const
-{
- assert (_ltc_input);
- return _ltc_input->nth (0);
-}
-
boost::shared_ptr<Port>
Session::ltc_output_port () const
{
return _ltc_output ? _ltc_output->nth (0) : boost::shared_ptr<Port> ();
}
-void
-Session::reconnect_ltc_input ()
-{
- if (_ltc_input) {
-
- string src = Config->get_ltc_source_port();
-
- _ltc_input->disconnect (this);
-
- if (src != _("None") && !src.empty()) {
- _ltc_input->nth (0)->connect (src);
- }
-
- if ( ARDOUR::Profile->get_trx () ) {
- // Tracks need this signal to update timecode_source_dropdown
- MtcOrLtcInputPortChanged (); //emit signal
- }
- }
-}
-
void
Session::reconnect_ltc_output ()
{
* modifies the capture-offset, which can be a problem.
*/
while (g_atomic_int_and (&_latency_recompute_pending, 0)) {
+ Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
update_latency_compensation ();
}
}
set_controls (stripable_list_to_control_list (sl, &Stripable::solo_control), 0.0, Controllable::NoGroup);
clear_all_solo_state (routes.reader());
}
+
+void
+Session::maybe_update_tempo_from_midiclock_tempo (float bpm)
+{
+ if (_tempo_map->n_tempos() == 1) {
+ TempoSection& ts (_tempo_map->tempo_section_at_sample (0));
+ if (fabs (ts.note_types_per_minute() - bpm) > (0.01 * ts.note_types_per_minute())) {
+ const Tempo tempo (bpm, 4.0, bpm);
+ std::cerr << "new tempo " << bpm << " old " << ts.note_types_per_minute() << std::endl;
+ _tempo_map->replace_tempo (ts, tempo, 0.0, 0.0, AudioTime);
+ }
+ }
+}