ardour/libs/ardour/session.cc

7709 lines
207 KiB
C++

/*
* 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 <algorithm>
#include <string>
#include <vector>
#include <sstream>
#include <cstdio> /* sprintf(3) ... grrr */
#include <cmath>
#include <cerrno>
#include <unistd.h>
#include <limits.h>
#include <glibmm/datetime.h>
#include <glibmm/threads.h>
#include <glibmm/miscutils.h>
#include <glibmm/fileutils.h>
#include <boost/algorithm/string/erase.hpp>
#include "pbd/basename.h"
#include "pbd/convert.h"
#include "pbd/error.h"
#include "pbd/file_utils.h"
#include "pbd/md5.h"
#include "pbd/pthread_utils.h"
#include "pbd/search_path.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/analyser.h"
#include "ardour/async_midi_port.h"
#include "ardour/audio_buffer.h"
#include "ardour/audio_port.h"
#include "ardour/audio_track.h"
#include "ardour/audioengine.h"
#include "ardour/audiofilesource.h"
#include "ardour/auditioner.h"
#include "ardour/boost_debug.h"
#include "ardour/buffer_manager.h"
#include "ardour/buffer_set.h"
#include "ardour/bundle.h"
#include "ardour/butler.h"
#include "ardour/click.h"
#include "ardour/control_protocol_manager.h"
#include "ardour/data_type.h"
#include "ardour/debug.h"
#include "ardour/disk_reader.h"
#include "ardour/directory_names.h"
#include "ardour/filename_extensions.h"
#include "ardour/gain_control.h"
#include "ardour/graph.h"
#include "ardour/io_plug.h"
#include "ardour/luabindings.h"
#include "ardour/midiport_manager.h"
#include "ardour/scene_changer.h"
#include "ardour/midi_patch_manager.h"
#include "ardour/midi_track.h"
#include "ardour/midi_ui.h"
#include "ardour/mixer_scene.h"
#include "ardour/operations.h"
#include "ardour/playlist.h"
#include "ardour/playlist_factory.h"
#include "ardour/plugin.h"
#include "ardour/plugin_insert.h"
#include "ardour/plugin_manager.h"
#include "ardour/polarity_processor.h"
#include "ardour/presentation_info.h"
#include "ardour/process_thread.h"
#include "ardour/profile.h"
#include "ardour/rc_configuration.h"
#include "ardour/recent_sessions.h"
#include "ardour/region.h"
#include "ardour/region_factory.h"
#include "ardour/revision.h"
#include "ardour/route_group.h"
#include "ardour/rt_tasklist.h"
#include "ardour/rt_safe_delete.h"
#include "ardour/silentfilesource.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/session_route.h"
#include "ardour/smf_source.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_fsm.h"
#include "ardour/transport_master.h"
#include "ardour/transport_master_manager.h"
#include "ardour/track.h"
#include "ardour/triggerbox.h"
#include "ardour/types_convert.h"
#include "ardour/user_bundle.h"
#include "ardour/utils.h"
#include "ardour/vca_manager.h"
#include "ardour/vca.h"
#include "midi++/port.h"
#include "midi++/mmc.h"
#include "LuaBridge/LuaBridge.h"
#include <glibmm/checksum.h>
#include "pbd/i18n.h"
namespace ARDOUR {
class MidiSource;
class Processor;
class Speakers;
}
using namespace std;
using namespace ARDOUR;
using namespace PBD;
using namespace Temporal;
bool Session::_disable_all_loaded_plugins = false;
bool Session::_bypass_all_loaded_plugins = false;
guint Session::_name_id_counter = 0;
PBD::Signal1<void,std::string> Session::Dialog;
PBD::Signal0<int> Session::AskAboutPendingState;
PBD::Signal2<int, samplecnt_t, samplecnt_t> Session::AskAboutSampleRateMismatch;
PBD::Signal2<void, samplecnt_t, samplecnt_t> Session::NotifyAboutSampleRateMismatch;
PBD::Signal0<void> Session::SendFeedback;
PBD::Signal3<int,Session*,std::string,DataType> Session::MissingFile;
PBD::Signal1<void, samplepos_t> Session::StartTimeChanged;
PBD::Signal1<void, samplepos_t> Session::EndTimeChanged;
PBD::Signal4<void, std::string, std::string, bool, samplepos_t> Session::Exported;
PBD::Signal1<int,boost::shared_ptr<Playlist> > Session::AskAboutPlaylistDeletion;
PBD::Signal0<void> Session::Quit;
PBD::Signal0<void> Session::FeedbackDetected;
PBD::Signal0<void> Session::SuccessfulGraphSort;
PBD::Signal2<void,std::string,std::string> Session::VersionMismatch;
PBD::Signal0<void> Session::AfterConnect;
const samplecnt_t Session::bounce_chunk_size = 8192;
static void clean_up_session_event (SessionEvent* ev) { delete ev; }
const SessionEvent::RTeventCallback Session::rt_cleanup (clean_up_session_event);
const uint32_t Session::session_end_shift = 0;
/** @param snapshot_name Snapshot name, without .ardour suffix */
Session::Session (AudioEngine &eng,
const string& fullpath,
const string& snapshot_name,
BusProfile const * bus_profile,
string mix_template,
bool unnamed)
: _playlists (new SessionPlaylists)
, _engine (eng)
, process_function (&Session::process_with_events)
, _bounce_processing_active (false)
, waiting_for_sync_offset (false)
, _base_sample_rate (0)
, _nominal_sample_rate (0)
, _current_sample_rate (0)
, _transport_sample (0)
, _session_range_location (0)
, _session_range_is_free (true)
, _silent (false)
, _remaining_latency_preroll (0)
, _last_touched_mixer_scene_idx (std::numeric_limits<size_t>::max())
, _engine_speed (1.0)
, _signalled_varispeed (0)
, auto_play_legal (false)
, _requested_return_sample (-1)
, current_block_size (0)
, _worst_output_latency (0)
, _worst_input_latency (0)
, _worst_route_latency (0)
, _io_latency (0)
, _send_latency_changes (0)
, _update_send_delaylines (false)
, _have_captured (false)
, _capture_duration (0)
, _capture_xruns (0)
, _export_xruns (0)
, _non_soloed_outs_muted (false)
, _listening (false)
, _listen_cnt (0)
, _solo_isolated_cnt (0)
, _writable (false)
, _under_nsm_control (false)
, _xrun_count (0)
, master_wait_end (0)
, post_export_sync (false)
, post_export_position (0)
, _exporting (false)
, _export_rolling (false)
, _realtime_export (false)
, _region_export (false)
, _export_preroll (0)
, _pre_export_mmc_enabled (false)
, _name (snapshot_name)
, _is_new (true)
, _send_qf_mtc (false)
, _pframes_since_last_mtc (0)
, play_loop (false)
, loop_changing (false)
, last_loopend (0)
, _session_dir (new SessionDirectory (fullpath))
, _current_snapshot_name (snapshot_name)
, state_tree (0)
, state_was_pending (false)
, _state_of_the_state (StateOfTheState (CannotSave | InitialConnecting | Loading))
, _save_queued (false)
, _save_queued_pending (false)
, _last_roll_location (0)
, _last_roll_or_reversal_location (0)
, _last_record_location (0)
, pending_auto_loop (false)
, _mempool ("Session", 3145728)
, lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool))
, _n_lua_scripts (0)
, _io_plugins (new IOPlugList)
, _butler (new Butler (*this))
, _transport_fsm (new TransportFSM (*this))
, _locations (new Locations (*this))
, _ignore_skips_updates (false)
, _rt_thread_active (false)
, _rt_emit_pending (false)
, _ac_thread_active (0)
, step_speed (0)
, outbound_mtc_timecode_frame (0)
, next_quarter_frame_to_send (-1)
, _samples_per_timecode_frame (0)
, _frames_per_hour (0)
, _timecode_frames_per_hour (0)
, last_timecode_valid (false)
, last_timecode_when (0)
, _send_timecode_update (false)
, ltc_encoder (0)
, ltc_enc_buf(0)
, ltc_buf_off (0)
, ltc_buf_len (0)
, ltc_speed (0)
, ltc_enc_byte (0)
, ltc_enc_pos (0)
, ltc_enc_cnt (0)
, ltc_enc_off (0)
, restarting (false)
, ltc_prev_cycle (0)
, ltc_timecode_offset (0)
, ltc_timecode_negative_offset (false)
, midi_control_ui (0)
, _punch_or_loop (NoConstraint)
, _all_route_group (new RouteGroup (*this, "all"))
, routes (new RouteList)
, _adding_routes_in_progress (false)
, _reconnecting_routes_in_progress (false)
, _route_deletion_in_progress (false)
, _route_reorder_in_progress (false)
, _track_number_decimals(1)
, default_fade_steepness (0)
, default_fade_msecs (0)
, _total_free_4k_blocks (0)
, _total_free_4k_blocks_uncertain (false)
, no_questions_about_missing_files (false)
, _bundles (new BundleList)
, _bundle_xml_node (0)
, _current_trans (0)
, _clicking (false)
, _click_rec_only (false)
, click_data (0)
, click_emphasis_data (0)
, click_length (0)
, click_emphasis_length (0)
, _clicks_cleared (0)
, _count_in_samples (0)
, _play_range (false)
, _range_selection (timepos_t::max (Temporal::AudioTime), timepos_t::max (Temporal::AudioTime))
, _object_selection (timepos_t::max (Temporal::AudioTime), timepos_t::max (Temporal::AudioTime))
, _preroll_record_trim_len (0)
, _count_in_once (false)
, main_outs (0)
, first_file_data_format_reset (true)
, first_file_header_format_reset (true)
, have_looped (false)
, _step_editors (0)
, _speakers (new Speakers)
, _ignore_route_processor_changes (0)
, _ignored_a_processor_change (0)
, midi_clock (0)
, _scene_changer (0)
, _midi_ports (0)
, _mmc (0)
, _vca_manager (new VCAManager (*this))
, _selection (new CoreSelection (*this))
, _global_locate_pending (false)
, _had_destructive_tracks (false)
, _pending_cue (-1)
, _active_cue (-1)
, tb_with_filled_slots (0)
{
g_atomic_int_set (&_suspend_save, 0);
g_atomic_int_set (&_playback_load, 0);
g_atomic_int_set (&_capture_load, 0);
g_atomic_int_set (&_post_transport_work, 0);
g_atomic_int_set (&_processing_prohibited, Disabled);
g_atomic_int_set (&_record_status, Disabled);
g_atomic_int_set (&_punch_or_loop, NoConstraint);
g_atomic_int_set (&_current_usecs_per_track, 1000);
g_atomic_int_set (&_have_rec_enabled_track, 0);
g_atomic_int_set (&_have_rec_disabled_track, 1);
g_atomic_int_set (&_latency_recompute_pending, 0);
g_atomic_int_set (&_suspend_timecode_transmission, 0);
g_atomic_int_set (&_update_pretty_names, 0);
g_atomic_int_set (&_seek_counter, 0);
g_atomic_int_set (&_butler_seek_counter, 0);
created_with = string_compose ("%1 %2", PROGRAM_NAME, revision);
pthread_mutex_init (&_rt_emit_mutex, 0);
pthread_cond_init (&_rt_emit_cond, 0);
pthread_mutex_init (&_auto_connect_mutex, 0);
pthread_cond_init (&_auto_connect_cond, 0);
init_name_id_counter (1); // reset for new sessions, start at 1
VCA::set_next_vca_number (1); // reset for new sessions, start at 1
_cue_events.reserve (1024);
pre_engine_init (fullpath); // sets _is_new
setup_lua ();
/* The engine sould be running at this point */
if (!AudioEngine::instance()->running()) {
destroy ();
throw SessionException (_("Session initialization failed because Audio/MIDI engine is not running."));
}
immediately_post_engine ();
bool need_template_resave = false;
std::string template_description;
if (_is_new) {
Stateful::loading_state_version = CURRENT_SESSION_FILE_VERSION;
if (create (mix_template, bus_profile, unnamed)) {
destroy ();
throw SessionException (_("Session initialization failed"));
}
/* if a mix template was provided, then ::create() will
* have copied it into the session and we need to load it
* so that we have the state ready for ::set_state()
* after the engine is started.
*
* Note that we do NOT try to get the sample rate from
* the template at this time, though doing so would
* be easy if we decided this was an appropriate part
* of a template.
*/
if (!mix_template.empty()) {
try {
if (load_state (_current_snapshot_name, /* from_template = */ true)) {
destroy ();
throw SessionException (_("Failed to load template/snapshot state"));
}
} catch (PBD::unknown_enumeration& e) {
destroy ();
throw SessionException (_("Failed to parse template/snapshot state"));
}
if (state_tree && Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION) {
need_template_resave = true;
XMLNode const & root (*state_tree->root());
XMLNode* desc_nd = root.child (X_("description"));
if (desc_nd) {
template_description = desc_nd->attribute_value();
}
}
store_recent_templates (mix_template);
}
/* load default session properties - if any */
config.load_state();
} else {
if (load_state (_current_snapshot_name)) {
destroy ();
throw SessionException (_("Failed to load state"));
}
ensure_subdirs (); // archived or zipped sessions may lack peaks/ analysis/ etc
}
/* apply the loaded state_tree */
int err = post_engine_init ();
if (err) {
destroy ();
switch (err) {
case -1:
throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Failed to create background threads.")));
break;
case -2:
case -3:
throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Invalid TempoMap in session-file.")));
break;
case -4:
throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Invalid or corrupt session state.")));
break;
case -5:
throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Port registration failed.")));
break;
case -6:
throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Audio/MIDI Engine is not running or sample-rate mismatches.")));
break;
default:
throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Unexpected exception during session setup, possibly invalid audio/midi engine parameters. Please see stdout/stderr for details")));
break;
}
}
if (!mix_template.empty()) {
/* fixup monitor-sends */
if (Config->get_use_monitor_bus ()) {
/* Session::config_changed will have set use-monitor-bus to match the template.
* search for want_ms, have_ms
*/
assert (_monitor_out);
/* ..but sends do not exist, since templated track bitslots are unset */
setup_route_monitor_sends (true, true);
} else {
/* remove any monitor-sends that may be in the template */
assert (!_monitor_out);
setup_route_monitor_sends (false, true);
}
}
if (!unnamed) {
store_recent_sessions (_name, _path);
}
bool was_dirty = dirty();
unset_dirty ();
PresentationInfo::Change.connect_same_thread (*this, boost::bind (&Session::notify_presentation_info_change, this, _1));
Config->ParameterChanged.connect_same_thread (*this, boost::bind (&Session::config_changed, this, _1, false));
config.ParameterChanged.connect_same_thread (*this, boost::bind (&Session::config_changed, this, _1, true));
if (was_dirty) {
DirtyChanged (); /* EMIT SIGNAL */
}
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));
LatentSend::ChangedLatency.connect_same_thread (*this, boost::bind (&Session::send_latency_compensation_change, this));
LatentSend::QueueUpdate.connect_same_thread (*this, boost::bind (&Session::update_send_delaylines, this));
Latent::DisableSwitchChanged.connect_same_thread (*this, boost::bind (&Session::queue_latency_recompute, this));
Controllable::ControlTouched.connect_same_thread (*this, boost::bind (&Session::controllable_touched, this, _1));
Location::cue_change.connect_same_thread (*this, boost::bind (&Session::cue_marker_change, this, _1));
IOPluginsChanged.connect_same_thread (*this, boost::bind (&Session::resort_io_plugs, this));
TempoMap::MapChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this));
emit_thread_start ();
auto_connect_thread_start ();
/* hook us up to the engine since we are now completely constructed */
BootMessage (_("Connect to engine"));
_engine.set_session (this);
_engine.reset_timebase ();
if (!mix_template.empty ()) {
/* ::create() unsets _is_new after creating the session.
* But for templated sessions, the sample-rate is initially unset
* (not read from template), so we need to save it (again).
*/
_is_new = true;
}
session_loaded ();
_is_new = false;
if (need_template_resave) {
save_template (mix_template, template_description, true);
}
BootMessage (_("Session loading complete"));
}
Session::~Session ()
{
#ifdef PT_TIMING
ST.dump ("ST.dump");
#endif
destroy ();
}
unsigned int
Session::next_name_id ()
{
return g_atomic_int_add (&_name_id_counter, 1);
}
unsigned int
Session::name_id_counter ()
{
return g_atomic_int_get (&_name_id_counter);
}
void
Session::init_name_id_counter (guint n)
{
g_atomic_int_set (&_name_id_counter, n);
}
int
Session::immediately_post_engine ()
{
/* Do various initializations that should take place directly after we
* know that the engine is running, but before we either create a
* session or set state for an existing one.
*/
_process_graph.reset (new Graph (*this));
_rt_tasklist.reset (new RTTaskList (_process_graph));
/* every time we reconnect, recompute worst case output latencies */
_engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this));
/* Restart transport FSM */
_transport_fsm->start ();
/* every time we reconnect, do stuff ... */
_engine.Running.connect_same_thread (*this, boost::bind (&Session::engine_running, this));
try {
BootMessage (_("Set up LTC"));
setup_ltc ();
BootMessage (_("Set up Click"));
setup_click ();
BootMessage (_("Set up standard connections"));
setup_bundles ();
}
catch (failed_constructor& err) {
return -1;
}
/* TODO, connect in different thread. (PortRegisteredOrUnregistered may be in RT context)
* can we do that? */
_engine.PortRegisteredOrUnregistered.connect_same_thread (*this, boost::bind (&Session::setup_bundles, this));
_engine.PortPrettyNameChanged.connect_same_thread (*this, boost::bind (&Session::setup_bundles, this));
// set samplerate for plugins added early
// e.g from templates or MB channelstrip
set_block_size (_engine.samples_per_cycle());
set_sample_rate (_engine.sample_rate());
return 0;
}
void
Session::destroy ()
{
vector<void*> debug_pointers;
/* if we got to here, leaving pending capture state around
is a mistake.
*/
remove_pending_capture_state ();
Analyser::flush ();
_state_of_the_state = StateOfTheState (CannotSave | Deletion);
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
ltc_tx_cleanup();
if (_ltc_output_port) {
AudioEngine::instance()->unregister_port (_ltc_output_port);
}
}
/* disconnect from any and all signals that we are connected to */
Port::PortSignalDrop (); /* EMIT SIGNAL */
drop_connections ();
/* stop auto dis/connecting */
auto_connect_thread_terminate ();
/* shutdown control surface protocols while we still have ports
* and the engine to move data to any devices.
*/
ControlProtocolManager::instance().drop_protocols ();
_engine.remove_session ();
/* deregister all ports - there will be no process or any other
* callbacks from the engine any more.
*/
Port::PortDrop (); /* EMIT SIGNAL */
/* remove I/O objects that we (the session) own */
_click_io.reset ();
_click_io_connection.disconnect ();
{
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 ();
/* clear state tree so that no references to objects are held any more */
delete state_tree;
state_tree = 0;
{
/* unregister all lua functions, drop held references (if any) */
Glib::Threads::Mutex::Lock tm (lua_lock, Glib::Threads::TRY_LOCK);
(*_lua_cleanup)();
lua.do_command ("Session = nil");
delete _lua_run;
delete _lua_add;
delete _lua_del;
delete _lua_list;
delete _lua_save;
delete _lua_load;
delete _lua_cleanup;
lua.collect_garbage ();
}
/* reset dynamic state version back to default */
Stateful::loading_state_version = 0;
/* drop GraphNode references */
_graph_chain.reset ();
_current_route_graph = GraphEdges ();
_io_graph_chain[0].reset ();
_io_graph_chain[1].reset ();
_butler->drop_references ();
delete _butler;
_butler = 0;
delete _all_route_group;
DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n");
for (list<RouteGroup *>::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) {
delete *i;
}
if (click_data != default_click) {
delete [] click_data;
}
if (click_emphasis_data != default_click_emphasis) {
delete [] click_emphasis_data;
}
clear_clicks ();
/* need to remove auditioner before monitoring section
* otherwise it is re-connected.
* Note: If a session was never successfully loaded, there
* may not yet be an auditioner.
*/
if (auditioner) {
auditioner->drop_references ();
}
auditioner.reset ();
/* unregister IO Plugin */
{
RCUWriter<IOPlugList> writer (_io_plugins);
boost::shared_ptr<IOPlugList> iop = writer.get_copy ();
for (auto const& i : *iop) {
i->DropReferences ();
}
iop->clear ();
}
/* drop references to routes held by the monitoring section
* specifically _monitor_out aux/listen references */
remove_monitor_section();
/* clear out any pending dead wood from RCU managed objects */
routes.flush ();
_bundles.flush ();
_io_plugins.flush ();
DiskReader::free_working_buffers();
/* tell everyone who is still standing that we're about to die */
drop_references ();
/* tell everyone to drop references and delete objects as we go */
DEBUG_TRACE (DEBUG::Destruction, "delete regions\n");
RegionFactory::delete_all_regions ();
/* Do this early so that VCAs no longer hold references to routes */
DEBUG_TRACE (DEBUG::Destruction, "delete vcas\n");
delete _vca_manager;
DEBUG_TRACE (DEBUG::Destruction, "delete routes\n");
/* reset these three references to special routes before we do the usual route delete thing */
_master_out.reset ();
_monitor_out.reset ();
{
RCUWriter<RouteList> writer (routes);
boost::shared_ptr<RouteList> r = writer.get_copy ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for route %1 ; pre-ref = %2\n", (*i)->name(), (*i).use_count()));
(*i)->drop_references ();
}
r->clear ();
/* writer goes out of scope and updates master */
}
routes.flush ();
{
DEBUG_TRACE (DEBUG::Destruction, "delete sources\n");
Glib::Threads::Mutex::Lock lm (source_lock);
for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for source %1 ; pre-ref = %2\n", i->second->name(), i->second.use_count()));
i->second->drop_references ();
}
sources.clear ();
}
/* not strictly necessary, but doing it here allows the shared_ptr debugging to work */
_playlists.reset ();
emit_thread_terminate ();
pthread_cond_destroy (&_rt_emit_cond);
pthread_mutex_destroy (&_rt_emit_mutex);
pthread_cond_destroy (&_auto_connect_cond);
pthread_mutex_destroy (&_auto_connect_mutex);
delete _scene_changer; _scene_changer = 0;
delete midi_control_ui; midi_control_ui = 0;
delete _mmc; _mmc = 0;
delete _midi_ports; _midi_ports = 0;
delete _locations; _locations = 0;
delete midi_clock;
/* clear event queue, the session is gone, nobody is interested in
* those anymore, but they do leak memory if not removed
*/
while (!immediate_events.empty ()) {
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
SessionEvent *ev = immediate_events.front ();
DEBUG_TRACE (DEBUG::SessionEvents, string_compose ("Drop event: %1\n", enum_2_string (ev->type)));
immediate_events.pop_front ();
bool remove = true;
bool del = true;
switch (ev->type) {
case SessionEvent::AutoLoop:
case SessionEvent::Skip:
case SessionEvent::PunchIn:
case SessionEvent::PunchOut:
case SessionEvent::RangeStop:
case SessionEvent::RangeLocate:
case SessionEvent::RealTimeOperation:
process_rtop (ev);
del = false;
break;
default:
break;
}
if (remove) {
del = del && !_remove_event (ev);
}
if (del) {
delete ev;
}
}
{
/* unregister all dropped ports, process pending port deletion. */
// this may call ARDOUR::Port::drop ... jack_port_unregister ()
// jack1 cannot cope with removing ports while processing
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
AudioEngine::instance()->clear_pending_port_deletions ();
}
DEBUG_TRACE (DEBUG::Destruction, "delete selection\n");
delete _selection;
_selection = 0;
_transport_fsm->stop ();
DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n");
#ifndef NDEBUG
Controllable::dump_registry ();
#endif
BOOST_SHOW_POINTERS ();
}
void
Session::block_processing()
{
g_atomic_int_set (&_processing_prohibited, 1);
/* processing_blocked() is only checked at the beginning
* of the next cycle. So wait until any ongoing
* process-callback returns.
*/
Glib::Threads::Mutex::Lock lm (_engine.process_lock());
/* latency callback may be in process, wait until it completed */
Glib::Threads::Mutex::Lock lx (_engine.latency_lock());
}
void
Session::setup_ltc ()
{
_ltc_output_port = AudioEngine::instance()->register_output_port (DataType::AUDIO, X_("LTC-Out"), false, TransportGenerator);
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
/* TODO use auto-connect thread */
reconnect_ltc_output ();
}
}
void
Session::setup_click ()
{
_clicking = false;
boost::shared_ptr<AutomationList> gl (new AutomationList (Evoral::Parameter (GainAutomation), Temporal::AudioTime));
boost::shared_ptr<GainControl> gain_control = boost::shared_ptr<GainControl> (new GainControl (*this, Evoral::Parameter(GainAutomation), gl));
_click_io.reset (new ClickIO (*this, X_("Click")));
_click_gain.reset (new Amp (*this, _("Fader"), gain_control, true));
_click_gain->activate ();
if (state_tree) {
setup_click_state (state_tree->root());
} else {
setup_click_state (0);
}
click_io_resync_latency (true);
LatencyUpdated.connect_same_thread (_click_io_connection, boost::bind (&Session::click_io_resync_latency, this, _1));
}
void
Session::setup_click_state (const XMLNode* node)
{
const XMLNode* child = 0;
if (node && (child = find_named_node (*node, "Click")) != 0) {
/* existing state for Click */
int c = 0;
if (Stateful::loading_state_version < 3000) {
c = _click_io->set_state_2X (*child->children().front(), Stateful::loading_state_version, false);
} else {
const XMLNodeList& children (child->children());
XMLNodeList::const_iterator i = children.begin();
if ((c = _click_io->set_state (**i, Stateful::loading_state_version)) == 0) {
++i;
if (i != children.end()) {
c = _click_gain->set_state (**i, Stateful::loading_state_version);
}
}
}
if (c == 0) {
_clicking = Config->get_clicking ();
} else {
error << _("could not setup Click I/O") << endmsg;
_clicking = false;
}
} else {
/* default state for Click: dual-mono to first 2 physical outputs */
vector<string> outs;
_engine.get_physical_outputs (DataType::AUDIO, outs);
for (uint32_t physport = 0; physport < 2; ++physport) {
if (outs.size() > physport) {
if (_click_io->add_port (outs[physport], this)) {
// relax, even though its an error
}
}
}
if (_click_io->n_ports () > ChanCount::ZERO) {
_clicking = Config->get_clicking ();
}
}
}
void
Session::get_physical_ports (vector<string>& inputs, vector<string>& outputs, DataType type,
MidiPortFlags include, MidiPortFlags exclude)
{
_engine.get_physical_inputs (type, inputs, include, exclude);
_engine.get_physical_outputs (type, outputs, include, exclude);
}
void
Session::auto_connect_master_bus ()
{
if (!_master_out || !Config->get_auto_connect_standard_busses() || _monitor_out) {
return;
}
/* if requested auto-connect the outputs to the first N physical ports.
*/
uint32_t limit = _master_out->n_outputs().n_total();
vector<string> outputs[DataType::num_types];
for (uint32_t i = 0; i < DataType::num_types; ++i) {
_engine.get_physical_outputs (DataType (DataType::Symbol (i)), outputs[i]);
}
for (uint32_t n = 0; n < limit; ++n) {
boost::shared_ptr<Port> p = _master_out->output()->nth (n);
string connect_to;
if (outputs[p->type()].size() > n) {
connect_to = outputs[p->type()][n];
}
if (!connect_to.empty() && p->connected_to (connect_to) == false) {
if (_master_out->output()->connect (p, connect_to, this)) {
error << string_compose (_("cannot connect master output %1 to %2"), n, connect_to)
<< endmsg;
break;
}
}
}
}
boost::shared_ptr<GainControl>
Session::master_volume () const
{
if (_master_out) {
return _master_out->volume_control ();
}
return boost::shared_ptr<GainControl> ();
}
void
Session::remove_monitor_section ()
{
if (!_monitor_out) {
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);
/* if we are auditioning, cancel it ... this is a workaround
to a problem (auditioning does not execute the process graph,
which is needed to remove routes when using >1 core for processing)
*/
cancel_audition ();
if (!deletion_in_progress ()) {
setup_route_monitor_sends (false, true);
_engine.monitor_port().clear_ports (true);
}
remove_route (_monitor_out);
if (deletion_in_progress ()) {
return;
}
auto_connect_master_bus ();
if (auditioner) {
auditioner->connect ();
}
MonitorBusAddedOrRemoved (); /* EMIT SIGNAL */
}
void
Session::add_monitor_section ()
{
RouteList rl;
if (!_engine.running()) {
error << _("Cannot create monitor section while the engine is offline.") << endmsg;
return;
}
if (_monitor_out || !_master_out) {
return;
}
boost::shared_ptr<Route> r (new Route (*this, _("Monitor"), PresentationInfo::MonitorOut, DataType::AUDIO));
if (r->init ()) {
return;
}
BOOST_MARK_ROUTE(r);
try {
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
r->input()->ensure_io (_master_out->output()->n_ports(), false, this);
r->output()->ensure_io (_master_out->output()->n_ports(), false, this);
} catch (...) {
error << _("Cannot create monitor section. 'Monitor' Port name is not unique.") << endmsg;
return;
}
rl.push_back (r);
add_routes (rl, false, false, 0);
assert (_monitor_out);
/* AUDIO ONLY as of june 29th 2009, because listen semantics for anything else
are undefined, at best.
*/
uint32_t limit = _monitor_out->n_inputs().n_audio();
if (_master_out) {
/* connect the inputs to the master bus outputs. this
* represents a separate data feed from the internal sends from
* each route. as of jan 2011, it allows the monitor section to
* conditionally ignore either the internal sends or the normal
* input feed, but we should really find a better way to do
* this, i think.
*/
_master_out->output()->disconnect (this);
for (uint32_t n = 0; n < limit; ++n) {
boost::shared_ptr<AudioPort> p = _monitor_out->input()->ports().nth_audio_port (n);
boost::shared_ptr<AudioPort> o = _master_out->output()->ports().nth_audio_port (n);
if (o) {
string connect_to = o->name();
if (_monitor_out->input()->connect (p, connect_to, this)) {
error << string_compose (_("cannot connect control input %1 to %2"), n, connect_to)
<< endmsg;
break;
}
}
}
}
/* if monitor section is not connected, connect it to physical outs */
if ((Config->get_auto_connect_standard_busses () || Profile->get_mixbus ()) && !_monitor_out->output()->connected ()) {
if (!Config->get_monitor_bus_preferred_bundle().empty()) {
boost::shared_ptr<Bundle> b = bundle_by_name (Config->get_monitor_bus_preferred_bundle());
if (b) {
_monitor_out->output()->connect_ports_to_bundle (b, true, this);
} else {
warning << string_compose (_("The preferred I/O for the monitor bus (%1) cannot be found"),
Config->get_monitor_bus_preferred_bundle())
<< endmsg;
}
} else {
/* Monitor bus is audio only */
vector<string> outputs[DataType::num_types];
for (uint32_t i = 0; i < DataType::num_types; ++i) {
_engine.get_physical_outputs (DataType (DataType::Symbol (i)), outputs[i]);
}
uint32_t mod = outputs[DataType::AUDIO].size();
uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO);
if (mod != 0) {
for (uint32_t n = 0; n < limit; ++n) {
boost::shared_ptr<Port> p = _monitor_out->output()->ports().port(DataType::AUDIO, n);
string connect_to;
if (outputs[DataType::AUDIO].size() > (n % mod)) {
connect_to = outputs[DataType::AUDIO][n % mod];
}
if (!connect_to.empty()) {
if (_monitor_out->output()->connect (p, connect_to, this)) {
error << string_compose (
_("cannot connect control output %1 to %2"),
n, connect_to)
<< endmsg;
break;
}
}
}
}
}
}
/* Hold process lock while doing this so that we don't hear bits and
* pieces of audio as we work on each route.
*/
setup_route_monitor_sends (true, true);
MonitorBusAddedOrRemoved (); /* EMIT SIGNAL */
}
void
Session::setup_route_monitor_sends (bool enable, bool need_process_lock)
{
Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock (), Glib::Threads::NOT_LOCK);
if (need_process_lock) {
/* Hold process lock while doing this so that we don't hear bits and
* pieces of audio as we work on each route.
*/
lx.acquire();
}
boost::shared_ptr<RouteList> rls = routes.reader ();
ProcessorChangeBlocker pcb (this, false /* XXX */);
for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) {
if ((*x)->can_monitor ()) {
if (enable) {
(*x)->enable_monitor_send ();
} else {
(*x)->remove_monitor_send ();
}
}
}
if (auditioner) {
auditioner->connect ();
}
}
void
Session::reset_monitor_section ()
{
/* Process lock should be held by the caller.*/
if (!_monitor_out) {
return;
}
uint32_t limit = _master_out->n_outputs().n_audio();
/* connect the inputs to the master bus outputs. this
* represents a separate data feed from the internal sends from
* each route. as of jan 2011, it allows the monitor section to
* conditionally ignore either the internal sends or the normal
* input feed, but we should really find a better way to do
* this, i think.
*/
_master_out->output()->disconnect (this);
_monitor_out->output()->disconnect (this);
// monitor section follow master bus - except midi
ChanCount mon_chn (_master_out->output()->n_ports());
mon_chn.set_midi (0);
_monitor_out->input()->ensure_io (mon_chn, false, this);
_monitor_out->output()->ensure_io (mon_chn, false, this);
for (uint32_t n = 0; n < limit; ++n) {
boost::shared_ptr<AudioPort> p = _monitor_out->input()->ports().nth_audio_port (n);
boost::shared_ptr<AudioPort> o = _master_out->output()->ports().nth_audio_port (n);
if (o) {
string connect_to = o->name();
if (_monitor_out->input()->connect (p, connect_to, this)) {
error << string_compose (_("cannot connect control input %1 to %2"), n, connect_to)
<< endmsg;
break;
}
}
}
/* connect monitor section to physical outs */
if (Config->get_auto_connect_standard_busses()) {
if (!Config->get_monitor_bus_preferred_bundle().empty()) {
boost::shared_ptr<Bundle> b = bundle_by_name (Config->get_monitor_bus_preferred_bundle());
if (b) {
_monitor_out->output()->connect_ports_to_bundle (b, true, this);
} else {
warning << string_compose (_("The preferred I/O for the monitor bus (%1) cannot be found"),
Config->get_monitor_bus_preferred_bundle())
<< endmsg;
}
} else {
/* Monitor bus is audio only */
vector<string> outputs[DataType::num_types];
for (uint32_t i = 0; i < DataType::num_types; ++i) {
_engine.get_physical_outputs (DataType (DataType::Symbol (i)), outputs[i]);
}
uint32_t mod = outputs[DataType::AUDIO].size();
uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO);
if (mod != 0) {
for (uint32_t n = 0; n < limit; ++n) {
boost::shared_ptr<Port> p = _monitor_out->output()->ports().port(DataType::AUDIO, n);
string connect_to;
if (outputs[DataType::AUDIO].size() > (n % mod)) {
connect_to = outputs[DataType::AUDIO][n % mod];
}
if (!connect_to.empty()) {
if (_monitor_out->output()->connect (p, connect_to, this)) {
error << string_compose (
_("cannot connect control output %1 to %2"),
n, connect_to)
<< endmsg;
break;
}
}
}
}
}
}
setup_route_monitor_sends (true, false);
}
int
Session::add_master_bus (ChanCount const& count)
{
if (master_out ()) {
return -1;
}
RouteList rl;
boost::shared_ptr<Route> r (new Route (*this, _("Master"), PresentationInfo::MasterOut, DataType::AUDIO));
if (r->init ()) {
return -1;
}
BOOST_MARK_ROUTE(r);
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
r->input()->ensure_io (count, false, this);
r->output()->ensure_io (count, false, this);
}
rl.push_back (r);
add_routes (rl, false, false, PresentationInfo::max_order);
return 0;
}
void
Session::hookup_io ()
{
/* stop graph reordering notifications from
causing resorts, etc.
*/
_state_of_the_state = StateOfTheState (_state_of_the_state | InitialConnecting);
if (!auditioner) {
/* we delay creating the auditioner till now because
it makes its own connections to ports.
*/
try {
boost::shared_ptr<Auditioner> a (new Auditioner (*this));
if (a->init()) {
throw failed_constructor ();
}
auditioner = a;
}
catch (failed_constructor& err) {
warning << _("cannot create Auditioner: no auditioning of regions possible") << endmsg;
}
}
/* load bundles, which we may have postponed earlier on */
if (_bundle_xml_node) {
load_bundles (*_bundle_xml_node);
delete _bundle_xml_node;
}
/* Get everything connected */
AudioEngine::instance()->reconnect_ports ();
AfterConnect (); /* EMIT SIGNAL */
/* Anyone who cares about input state, wake up and do something */
IOConnectionsComplete (); /* EMIT SIGNAL */
_state_of_the_state = StateOfTheState (_state_of_the_state & ~InitialConnecting);
/* now handle the whole enchilada as if it was one
graph reorder event.
*/
graph_reordered (false);
/* update the full solo state, which can't be
correctly determined on a per-route basis, but
needs the global overview that only the session
has.
*/
update_route_solo_state ();
}
void
Session::track_playlist_changed (boost::weak_ptr<Track> wp)
{
boost::shared_ptr<Track> track = wp.lock ();
if (!track) {
return;
}
boost::shared_ptr<Playlist> playlist;
if ((playlist = track->playlist()) != 0) {
playlist->RegionAdded.connect_same_thread (*this, boost::bind (&Session::playlist_region_added, this, _1));
playlist->RangesMoved.connect_same_thread (*this, boost::bind (&Session::playlist_ranges_moved, this, _1));
playlist->RegionsExtended.connect_same_thread (*this, boost::bind (&Session::playlist_regions_extended, this, _1));
}
}
bool
Session::record_enabling_legal () const
{
if (Config->get_all_safe()) {
return false;
}
return true;
}
void
Session::set_track_monitor_input_status (bool yn)
{
boost::shared_ptr<RouteList> rl = routes.reader ();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<AudioTrack> tr = boost::dynamic_pointer_cast<AudioTrack> (*i);
if (tr && tr->rec_enable_control()->get_value()) {
tr->request_input_monitoring (yn);
}
}
}
void
Session::auto_punch_start_changed (Location* location)
{
replace_event (SessionEvent::PunchIn, location->start_sample());
if (get_record_enabled() && config.get_punch_in() && !actively_recording ()) {
/* capture start has been changed, so save new pending state */
save_state ("", true);
}
}
bool
Session::punch_active () const
{
if (!get_record_enabled ()) {
return false;
}
if (!_locations->auto_punch_location ()) {
return false;
}
return config.get_punch_in () || config.get_punch_out ();
}
bool
Session::punch_is_possible () const
{
return g_atomic_int_get (&_punch_or_loop) != OnlyLoop;
}
bool
Session::loop_is_possible () const
{
#if 0 /* maybe prevent looping even when not rolling ? */
if (get_record_enabled () && punch_active ()) {
return false;
}
}
#endif
return g_atomic_int_get(&_punch_or_loop) != OnlyPunch;
}
void
Session::reset_punch_loop_constraint ()
{
if (g_atomic_int_get (&_punch_or_loop) == NoConstraint) {
return;
}
g_atomic_int_set (&_punch_or_loop, NoConstraint);
PunchLoopConstraintChange (); /* EMIT SIGNAL */
}
bool
Session::maybe_allow_only_loop (bool play_loop) {
if (!(get_play_loop () || play_loop)) {
return false;
}
bool rv = g_atomic_int_compare_and_exchange (&_punch_or_loop, NoConstraint, OnlyLoop);
if (rv) {
PunchLoopConstraintChange (); /* EMIT SIGNAL */
}
if (rv || loop_is_possible ()) {
unset_punch ();
return true;
}
return false;
}
bool
Session::maybe_allow_only_punch () {
if (!punch_active ()) {
return false;
}
bool rv = g_atomic_int_compare_and_exchange (&_punch_or_loop, NoConstraint, OnlyPunch);
if (rv) {
PunchLoopConstraintChange (); /* EMIT SIGNAL */
}
return rv || punch_is_possible ();
}
void
Session::unset_punch ()
{
/* used when enabling looping
* -> _punch_or_loop = OnlyLoop;
*/
if (config.get_punch_in ()) {
config.set_punch_in (false);
}
if (config.get_punch_out ()) {
config.set_punch_out (false);
}
}
void
Session::auto_punch_end_changed (Location* location)
{
replace_event (SessionEvent::PunchOut, location->end_sample());
}
void
Session::auto_punch_changed (Location* location)
{
auto_punch_start_changed (location);
auto_punch_end_changed (location);
}
void
Session::auto_loop_changed (Location* location)
{
if (!location) {
return;
}
replace_event (SessionEvent::AutoLoop, location->end_sample(), location->start_sample());
if (transport_rolling()) {
if (get_play_loop ()) {
if (_transport_sample < location->start_sample() || _transport_sample > location->end_sample()) {
/* new loop range excludes current transport
* sample => relocate to beginning of loop and roll.
*/
/* Set this so that when/if we have to stop the
* transport for a locate, we know that it is triggered
* by loop-changing, and we do not cancel play loop
*/
loop_changing = true;
request_locate (location->start_sample(), false, MustRoll);
} else {
// schedule a locate-roll to refill the diskstreams at the
// previous loop end
/* schedule a buffer overwrite to refill buffers with the new loop. */
SessionEvent *ev = new SessionEvent (SessionEvent::OverwriteAll, SessionEvent::Add, SessionEvent::Immediate, 0, 0, 0.0);
ev->overwrite = LoopChanged;
queue_event (ev);
}
}
} else {
/* possibly move playhead if not rolling; if we are rolling we'll move
to the loop start on stop if that is appropriate.
*/
samplepos_t pos;
if (select_playhead_priority_target (pos)) {
if (pos == location->start_sample()) {
request_locate (pos);
}
}
}
last_loopend = location->end_sample();
set_dirty ();
}
void
Session::set_auto_punch_location (Location* location)
{
Location* existing;
if ((existing = _locations->auto_punch_location()) != 0 && existing != location) {
punch_connections.drop_connections();
existing->set_auto_punch (false, this);
clear_events (SessionEvent::PunchIn);
clear_events (SessionEvent::PunchOut);
auto_punch_location_changed (0);
}
set_dirty();
if (location == 0) {
return;
}
if (location->end() <= location->start()) {
error << _("Session: you can't use that location for auto punch (start <= end)") << endmsg;
return;
}
punch_connections.drop_connections ();
location->StartChanged.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_start_changed, this, location));
location->EndChanged.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_end_changed, this, location));
location->Changed.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_changed, this, location));
location->set_auto_punch (true, this);
auto_punch_changed (location);
auto_punch_location_changed (location);
}
void
Session::set_session_extents (timepos_t const & start, timepos_t const & end)
{
if (end <= start) {
error << _("Session: you can't use that location for session start/end)") << endmsg;
return;
}
Location* existing;
if ((existing = _locations->session_range_location()) == 0) {
_session_range_location = new Location (*this, start, end, _("session"), Location::IsSessionRange);
_locations->add (_session_range_location);
} else {
existing->set( start, end );
}
set_dirty();
}
void
Session::set_auto_loop_location (Location* location)
{
Location* existing;
if ((existing = _locations->auto_loop_location()) != 0 && existing != location) {
loop_connections.drop_connections ();
existing->set_auto_loop (false, this);
remove_event (existing->end_sample(), SessionEvent::AutoLoop);
auto_loop_location_changed (0);
}
set_dirty();
if (location == 0) {
return;
}
if (location->end() <= location->start()) {
error << _("You cannot use this location for auto-loop because it has zero or negative length") << endmsg;
return;
}
last_loopend = location->end_sample();
loop_connections.drop_connections ();
location->StartChanged.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
location->EndChanged.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
location->Changed.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
location->FlagsChanged.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
location->set_auto_loop (true, this);
if (Config->get_loop_is_mode() && get_play_loop ()) {
/* set all tracks to use internal looping */
set_track_loop (true);
}
/* take care of our stuff first */
auto_loop_changed (location);
/* now tell everyone else */
auto_loop_location_changed (location);
}
void
Session::update_marks (Location*)
{
set_dirty ();
}
void
Session::update_skips (Location* loc, bool consolidate)
{
if (_ignore_skips_updates) {
return;
}
Locations::LocationList skips;
if (consolidate) {
PBD::Unwinder<bool> uw (_ignore_skips_updates, true);
consolidate_skips (loc);
}
sync_locations_to_skips ();
set_dirty ();
}
void
Session::consolidate_skips (Location* loc)
{
Locations::LocationList all_locations = _locations->list ();
for (Locations::LocationList::iterator l = all_locations.begin(); l != all_locations.end(); ) {
if (!(*l)->is_skip ()) {
++l;
continue;
}
/* don't test against self */
if (*l == loc) {
++l;
continue;
}
switch (Temporal::coverage_exclusive_ends ((*l)->start(), (*l)->end(), loc->start(), loc->end())) {
case Temporal::OverlapInternal:
case Temporal::OverlapExternal:
case Temporal::OverlapStart:
case Temporal::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 Temporal::OverlapNone:
++l;
break;
}
}
}
void
Session::sync_locations_to_skips ()
{
/* This happens asynchronously (in the audioengine thread). After the clear is done, we will call
* Session::_sync_locations_to_skips() from the audioengine thread.
*/
clear_events (SessionEvent::Skip, boost::bind (&Session::_sync_locations_to_skips, this));
}
void
Session::_sync_locations_to_skips ()
{
/* called as a callback after existing Skip events have been cleared from a realtime audioengine thread */
Locations::LocationList const & locs (_locations->list());
for (Locations::LocationList::const_iterator i = locs.begin(); i != locs.end(); ++i) {
Location* location = *i;
if (location->is_skip() && location->is_skipping()) {
SessionEvent* ev = new SessionEvent (SessionEvent::Skip, SessionEvent::Add, location->start_sample(), location->end_sample(), 1.0);
queue_event (ev);
}
}
}
void
Session::location_added (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_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 */
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->TimeDomainChanged.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 */
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->TimeDomainChanged.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 */
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->TimeDomainChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location));
update_skips (location, true);
}
set_dirty ();
}
void
Session::location_removed (Location *location)
{
if (location->is_auto_loop()) {
set_auto_loop_location (0);
if (!get_play_loop ()) {
set_track_loop (false);
}
unset_play_loop ();
}
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_skip()) {
update_skips (location, false);
}
set_dirty ();
}
void
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.
*/
{
PBD::Unwinder<bool> protect_ignore_skip_updates (_ignore_skips_updates, true);
for (Locations::LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
location_added (*i);
}
}
update_skips (NULL, false);
}
void
Session::enable_record ()
{
if (_transport_fsm->transport_speed() != 0.0 && _transport_fsm->transport_speed() != 1.0) {
/* no recording at anything except normal speed */
return;
}
while (1) {
RecordState rs = (RecordState) g_atomic_int_get (&_record_status);
if (rs == Recording) {
break;
}
if (g_atomic_int_compare_and_exchange (&_record_status, rs, Recording)) {
_last_record_location = _transport_sample;
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe));
if (Config->get_recording_resets_xrun_count ()) {
reset_xrun_count ();
}
if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
set_track_monitor_input_status (true);
}
_capture_duration = 0;
_capture_xruns = 0;
RecordStateChanged ();
break;
}
}
}
void
Session::set_all_tracks_record_enabled (bool enable )
{
boost::shared_ptr<RouteList> rl = routes.reader();
set_controls (route_list_to_control_list (rl, &Stripable::rec_enable_control), enable, Controllable::NoGroup);
}
void
Session::disable_record (bool rt_context, bool force)
{
RecordState rs;
if ((rs = (RecordState) g_atomic_int_get (&_record_status)) != Disabled) {
if (!Config->get_latched_record_enable () || force) {
g_atomic_int_set (&_record_status, Disabled);
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit));
} else {
if (rs == Recording) {
g_atomic_int_set (&_record_status, Enabled);
}
}
if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
set_track_monitor_input_status (false);
}
RecordStateChanged (); /* emit signal */
}
}
void
Session::step_back_from_record ()
{
if (g_atomic_int_compare_and_exchange (&_record_status, Recording, Enabled)) {
if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
set_track_monitor_input_status (false);
}
RecordStateChanged (); /* emit signal */
}
}
void
Session::maybe_enable_record (bool rt_context)
{
if (_step_editors > 0) {
return;
}
g_atomic_int_set (&_record_status, Enabled);
// TODO make configurable, perhaps capture-buffer-seconds dependnet?
bool quick_start = true;
/* Save pending state of which sources the next record will use,
* which gives us some chance of recovering from a crash during the record.
*/
if (!rt_context && (!quick_start || _transport_fsm->transport_speed() == 0)) {
save_state ("", true);
}
if (_transport_fsm->transport_speed() != 0) {
maybe_allow_only_punch ();
if (!config.get_punch_in()) {
enable_record ();
}
/* When rolling, start recording immediately.
* Do not wait for .pending state save to complete
* because that may take some time (up to a second
* for huge sessions).
*
* This is potentially dangerous!! If a crash happens
* while recording before the .pending save completed,
* the data until then may be lost or overwritten.
* (However disk-writer buffers are usually longer,
* compared to the time it takes to save a session.
* disk I/O may not be a bottleneck either. Except
* perhaps plugin-state saves taking a lock.
*/
if (!rt_context && quick_start) {
save_state ("", true);
}
} else {
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause));
RecordStateChanged (); /* EMIT SIGNAL */
}
set_dirty();
}
samplepos_t
Session::audible_sample (bool* latent_locate) const
{
if (latent_locate) {
*latent_locate = false;
}
samplepos_t ret;
if (synced_to_engine()) {
/* Note: this is basically just sync-to-JACK */
ret = _engine.transport_sample();
} else {
ret = _transport_sample;
}
assert (ret >= 0);
if (!transport_rolling()) {
return ret;
}
#if 0 // TODO looping
if (_transport_fsm->transport_speed() > 0.0f) {
if (play_loop && have_looped) {
/* the play-position wrapped at the loop-point
* ardour is already playing the beginning of the loop,
* but due to playback latency, the "audible frame"
* is still at the end of the loop.
*/
Location *location = _locations->auto_loop_location();
sampleoffset_t lo = location->start() - ret;
if (lo > 0) {
ret = location->end () - lo;
if (latent_locate) {
*latent_locate = true;
}
}
}
} else if (_transport_fsm->transport_speed() < 0.0f) {
/* XXX wot? no backward looping? */
}
#endif
return std::max ((samplepos_t)0, ret);
}
samplecnt_t
Session::preroll_samples (samplepos_t pos) const
{
const float pr = Config->get_preroll_seconds();
if (pos >= 0 && pr < 0) {
Temporal::TempoMetric const & metric (TempoMap::use()->metric_at (pos));
return metric.samples_per_bar (sample_rate()) * -pr;
}
if (pr < 0) {
return 0;
}
return pr * sample_rate();
}
void
Session::set_sample_rate (samplecnt_t frames_per_second)
{
/** \fn void Session::set_sample_size(samplecnt_t)
the AudioEngine object that calls this guarantees
that it will not be called while we are also in
::process(). Its fine to do things that block
here.
*/
if (_base_sample_rate == 0) {
_base_sample_rate = frames_per_second;
}
else if (_base_sample_rate != frames_per_second && frames_per_second != _nominal_sample_rate && _engine.running ()) {
NotifyAboutSampleRateMismatch (_base_sample_rate, frames_per_second);
}
_nominal_sample_rate = _base_sample_rate;
Temporal::set_sample_rate (_nominal_sample_rate);
sync_time_vars();
clear_clicks ();
reset_write_sources (false);
DiskReader::alloc_loop_declick (nominal_sample_rate());
Location* loc = _locations->auto_loop_location ();
DiskReader::reset_loop_declick (loc, nominal_sample_rate());
// XXX we need some equivalent to this, somehow
// SndFileSource::setup_standard_crossfades (frames_per_second);
set_dirty();
/* XXX need to reset/reinstantiate all LADSPA plugins */
}
void
Session::set_block_size (pframes_t nframes)
{
/* the AudioEngine guarantees
* that it will not be called while we are also in
* ::process(). It is therefore fine to do things that block
* here.
*/
current_block_size = nframes;
ensure_buffers ();
foreach_route (&Route::set_block_size, nframes);
boost::shared_ptr<IOPlugList> iop (_io_plugins.reader ());
for (auto const& i : *iop) {
i->set_block_size (nframes);
}
DEBUG_TRACE (DEBUG::LatencyCompensation, "Session::set_block_size -> update worst i/o latency\n");
/* when this is called from the auto-connect thread, the process-lock is held */
Glib::Threads::Mutex::Lock lx (_update_latency_lock);
set_worst_output_latency ();
set_worst_input_latency ();
}
void
Session::resort_routes ()
{
/* don't do anything here with signals emitted
by Routes during initial setup or while we
are being destroyed.
*/
if (inital_connect_or_deletion_in_progress ()) {
/* drop any references during delete */
GraphEdges edges;
_current_route_graph = edges;
return;
}
if (_route_deletion_in_progress) {
return;
}
{
RCUWriter<RouteList> writer (routes);
boost::shared_ptr<RouteList> r = writer.get_copy ();
resort_routes_using (r);
/* writer goes out of scope and forces update */
}
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::Graph)) {
DEBUG_TRACE (DEBUG::Graph, "---- Session::resort_routes ----\n");
for (auto const& i : *routes.reader ()) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("%1 fed by ...\n", i->name()));
for (auto const& f : i->signal_sources ()) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1\n", f->graph_node_name ()));
}
}
DEBUG_TRACE (DEBUG::Graph, "---- EOF ----\n");
}
#endif
}
/** This is called whenever we need to rebuild the graph of how we will process
* routes.
* @param r List of routes, in any order.
*/
void
Session::resort_routes_using (boost::shared_ptr<RouteList> r)
{
#ifndef NDEBUG
Timing t;
#endif
GraphNodeList gnl;
for (auto const& rt : *r) {
gnl.push_back (rt);
}
bool ok = true;
if (rechain_process_graph (gnl)) {
/* Update routelist for single-threaded processing, use topologically sorted nodelist */
r->clear ();
for (auto const& nd : gnl) {
r->push_back (boost::dynamic_pointer_cast<Route> (nd));
}
} else {
ok = false;
}
/* now create IOPlugs graph-chains */
boost::shared_ptr<IOPlugList> io_plugins (_io_plugins.reader ());
GraphNodeList gnl_pre;
GraphNodeList gnl_post;
for (auto const& p : *io_plugins) {
if (p->is_pre ()) {
gnl_pre.push_back (p);
} else {
gnl_post.push_back (p);
}
}
if (!rechain_ioplug_graph (true)) {
ok = false;
}
if (!rechain_ioplug_graph (false)) {
ok = false;
}
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::TopologyTiming)) {
t.update ();
std::cerr << string_compose ("Session::resort_route took %1ms ; DSP %2 %%\n",
t.elapsed () / 1000., 100.0 * t.elapsed() / _engine.usecs_per_cycle ());
DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n");
for (auto const& i : *r) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 (presentation order %2)\n", i->name(), i->presentation_info().order()));
}
}
#endif
if (ok) {
SuccessfulGraphSort (); /* EMIT SIGNAL */
return;
}
/* The topological sort failed, so we have a problem. Tell everyone
* and stick to the old graph; this will continue to be processed, so
* until the feedback is fixed, what is played back will not quite
* reflect what is actually connected.
*/
FeedbackDetected (); /* EMIT SIGNAL */
}
void
Session::resort_io_plugs ()
{
bool ok_pre = rechain_ioplug_graph (true);
bool ok_post = rechain_ioplug_graph (false);
if (!ok_pre || !ok_post) {
FeedbackDetected (); /* EMIT SIGNAL */
}
}
bool
Session::rechain_process_graph (GraphNodeList& g)
{
/* This may be called from the GUI thread (concurrrently with processing),
* when a user adds/removes routes.
*
* Or it may be called from the engine when connections are changed.
* In that case processing is blocked until the graph change is handled.
*/
GraphEdges edges;
if (topological_sort (g, edges)) {
/* We got a satisfactory topological sort, so there is no feedback;
* use this new graph.
*
* Note: the process graph chain does not require a
* topologically-sorted list, but hey ho.
*/
if (_process_graph->n_threads () > 1) {
/* Ideally we'd use a memory pool to allocate the GraphChain, however node_lists
* inside the change are STL list/set. It was never rt-safe to re-chain the graph.
* Furthermore graph-changes are usually caused by connection changes, which are not
* rt-safe either.
*
* However, the graph-chain may be in use (session process), and the last reference
* be helf by the process-callback. So we delegate deletion to the butler thread.
*/
_graph_chain = boost::shared_ptr<GraphChain> (new GraphChain (g, edges), boost::bind (&rt_safe_delete<GraphChain>, this, _1));
} else {
_graph_chain.reset ();
}
_current_route_graph = edges;
return true;
}
return false;
}
bool
Session::rechain_ioplug_graph (bool pre)
{
boost::shared_ptr<IOPlugList> io_plugins (_io_plugins.reader ());
if (io_plugins->empty ()) {
_io_graph_chain[pre ? 0 : 1].reset ();
return true;
}
GraphNodeList gnl;
for (auto const& p : *io_plugins) {
if (p->is_pre () == pre) {
gnl.push_back (p);
}
}
GraphEdges edges;
if (topological_sort (gnl, edges)) {
_io_graph_chain[pre ? 0 : 1] = boost::shared_ptr<GraphChain> (new GraphChain (gnl, edges), boost::bind (&rt_safe_delete<GraphChain>, this, _1));
return true;
}
return false;
}
/** Find a route name starting with \a base, maybe followed by the
* lowest \a id. \a id will always be added if \a definitely_add_number
* is true on entry; otherwise it will only be added if required
* to make the name unique.
*
* Names are constructed like e.g. "Audio 3" for base="Audio" and id=3.
* The available route name with the lowest ID will be used, and \a id
* will be set to the ID.
*
* \return false if a route name could not be found, and \a track_name
* and \a id do not reflect a free route name.
*/
bool
Session::find_route_name (string const & base, uint32_t& id, string& name, bool definitely_add_number)
{
/* the base may conflict with ports that do not belong to existing
routes, but hidden objects like the click track. So check port names
before anything else.
*/
for (map<string,bool>::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) {
if (base == reserved->first) {
/* Check if this reserved name already exists, and if
so, disallow it without a numeric suffix.
*/
if (!reserved->second || route_by_name (reserved->first)) {
definitely_add_number = true;
if (id < 1) {
id = 1;
}
}
break;
}
}
/* if we have "base 1" already, it doesn't make sense to add "base"
* if "base 1" has been deleted, adding "base" is no worse than "base 1"
*/
if (!definitely_add_number && route_by_name (base) == 0 && (route_by_name (string_compose("%1 1", base)) == 0)) {
/* just use the base */
name = base;
return true;
}
do {
name = string_compose ("%1 %2", base, id);
if (route_by_name (name) == 0) {
return true;
}
++id;
} while (id < (UINT_MAX-1));
return false;
}
/** Count the total ins and outs of all non-hidden tracks in the session and return them in in and out */
void
Session::count_existing_track_channels (ChanCount& in, ChanCount& out)
{
in = ChanCount::ZERO;
out = ChanCount::ZERO;
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;
}
assert (!tr->is_auditioner()); // XXX remove me
in += tr->n_inputs();
out += tr->n_outputs();
}
}
string
Session::default_track_name_pattern (DataType t)
{
switch (t) {
case DataType::AUDIO:
return _("Audio");
break;
case DataType::MIDI:
return _("MIDI");
}
return "";
}
/** Caller must not hold process lock
* @param name_template string to use for the start of the name, or "" to use "MIDI".
* @param instrument plugin info for the instrument to insert pre-fader, if any
*/
list<boost::shared_ptr<MidiTrack> >
Session::new_midi_track (const ChanCount& input, const ChanCount& output, bool strict_io,
boost::shared_ptr<PluginInfo> instrument, Plugin::PresetRecord* pset,
RouteGroup* route_group, uint32_t how_many,
string name_template, PresentationInfo::order_t order,
TrackMode mode, bool input_auto_connect,
bool trigger_visibility)
{
string track_name;
uint32_t track_id = 0;
string port;
RouteList new_routes;
list<boost::shared_ptr<MidiTrack> > ret;
const string name_pattern = default_track_name_pattern (DataType::MIDI);
bool const use_number = (how_many != 1) || name_template.empty () || (name_template == name_pattern);
while (how_many) {
if (!find_route_name (name_template.empty() ? _("MIDI") : name_template, ++track_id, track_name, use_number)) {
error << "cannot find name for new midi track" << endmsg;
goto failed;
}
boost::shared_ptr<MidiTrack> track;
try {
track.reset (new MidiTrack (*this, track_name, mode));
if (track->init ()) {
goto failed;
}
if (strict_io) {
track->set_strict_io (true);
}
BOOST_MARK_TRACK (track);
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
if (track->input()->ensure_io (input, false, this)) {
error << "cannot configure " << input << " out configuration for new midi track" << endmsg;
goto failed;
}
if (track->output()->ensure_io (output, false, this)) {
error << "cannot configure " << output << " out configuration for new midi track" << endmsg;
goto failed;
}
}
if (route_group) {
route_group->add (track);
}
track->presentation_info ().set_trigger_track (trigger_visibility);
new_routes.push_back (track);
ret.push_back (track);
}
catch (failed_constructor &err) {
error << _("Session: could not create new midi track.") << endmsg;
goto failed;
}
catch (AudioEngine::PortRegistrationFailure& pfe) {
error << string_compose (_("No more JACK ports are available. You will need to stop %1 and restart JACK with more ports if you need this many tracks."), PROGRAM_NAME) << endmsg;
goto failed;
}
--how_many;
}
failed:
if (!new_routes.empty()) {
ChanCount existing_inputs;
ChanCount existing_outputs;
count_existing_track_channels (existing_inputs, existing_outputs);
add_routes (new_routes, input_auto_connect, !instrument, order);
load_and_connect_instruments (new_routes, strict_io, instrument, pset, existing_outputs);
}
return ret;
}
RouteList
Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name_template, bool strict_io,
boost::shared_ptr<PluginInfo> instrument, Plugin::PresetRecord* pset,
PresentationInfo::Flag flag, PresentationInfo::order_t order)
{
string bus_name;
uint32_t bus_id = 0;
string port;
RouteList ret;
bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Midi Bus");
while (how_many) {
if (!find_route_name (name_template.empty () ? _("Midi Bus") : name_template, ++bus_id, bus_name, use_number)) {
error << "cannot find name for new midi bus" << endmsg;
goto failure;
}
try {
boost::shared_ptr<Route> bus (new Route (*this, bus_name, flag, DataType::AUDIO)); // XXX Editor::add_routes is not ready for ARDOUR::DataType::MIDI
if (bus->init ()) {
goto failure;
}
if (strict_io) {
bus->set_strict_io (true);
}
BOOST_MARK_ROUTE(bus);
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
if (bus->input()->ensure_io (ChanCount(DataType::MIDI, 1), false, this)) {
error << _("cannot configure new midi bus input") << endmsg;
goto failure;
}
if (bus->output()->ensure_io (ChanCount(DataType::MIDI, 1), false, this)) {
error << _("cannot configure new midi bus output") << endmsg;
goto failure;
}
}
if (route_group) {
route_group->add (bus);
}
bus->add_internal_return ();
ret.push_back (bus);
}
catch (failed_constructor &err) {
error << _("Session: could not create new MIDI bus.") << endmsg;
goto failure;
}
catch (AudioEngine::PortRegistrationFailure& pfe) {
error << pfe.what() << endmsg;
goto failure;
}
--how_many;
}
failure:
if (!ret.empty()) {
ChanCount existing_inputs;
ChanCount existing_outputs;
count_existing_track_channels (existing_inputs, existing_outputs);
add_routes (ret, false, !instrument, order);
load_and_connect_instruments (ret, strict_io, instrument, pset, existing_outputs);
}
return ret;
}
void
Session::midi_output_change_handler (IOChange change, void * /*src*/, boost::weak_ptr<Route> wr)
{
boost::shared_ptr<Route> midi_route (wr.lock());
if (!midi_route) {
return;
}
if ((change.type & IOChange::ConfigurationChanged) && Config->get_output_auto_connect() != ManualConnect) {
if (change.after.n_audio() <= change.before.n_audio()) {
return;
}
/* new audio ports: make sure the audio goes somewhere useful,
* unless the user has no-auto-connect selected.
*
* The existing ChanCounts don't matter for this call as they are only
* to do with matching input and output indices, and we are only changing
* outputs here.
*/
auto_connect_route (midi_route, false, !midi_route->instrument_fanned_out (), ChanCount(), change.before);
}
}
bool
Session::ensure_stripable_sort_order ()
{
StripableList sl;
get_stripables (sl);
sl.sort (Stripable::Sorter ());
bool change = false;
PresentationInfo::order_t order = 0;
for (StripableList::iterator si = sl.begin(); si != sl.end(); ++si) {
boost::shared_ptr<Stripable> s (*si);
assert (!s->is_auditioner ()); // XXX remove me
if (s->is_monitor ()) {
continue;
}
if (order != s->presentation_info().order()) {
s->set_presentation_order (order);
change = true;
}
++order;
}
return change;
}
void
Session::ensure_route_presentation_info_gap (PresentationInfo::order_t first_new_order, uint32_t how_many)
{
DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("ensure order gap starting at %1 for %2\n", first_new_order, how_many));
if (first_new_order == PresentationInfo::max_order) {
/* adding at end, no worries */
return;
}
/* create a gap in the presentation info to accommodate @p how_many
* new objects.
*/
StripableList sl;
get_stripables (sl);
for (StripableList::iterator si = sl.begin(); si != sl.end(); ++si) {
boost::shared_ptr<Stripable> s (*si);
if (s->presentation_info().special (false)) {
continue;
}
if (!s->presentation_info().order_set()) {
continue;
}
if (s->presentation_info().order () >= first_new_order) {
s->set_presentation_order (s->presentation_info().order () + how_many);
}
}
}
/** Caller must not hold process lock
* @param name_template string to use for the start of the name, or "" to use "Audio".
*/
list< boost::shared_ptr<AudioTrack> >
Session::new_audio_track (int input_channels, int output_channels, RouteGroup* route_group,
uint32_t how_many, string name_template, PresentationInfo::order_t order,
TrackMode mode, bool input_auto_connect,
bool trigger_visibility)
{
string track_name;
uint32_t track_id = 0;
string port;
RouteList new_routes;
list<boost::shared_ptr<AudioTrack> > ret;
const string name_pattern = default_track_name_pattern (DataType::AUDIO);
bool const use_number = (how_many != 1) || name_template.empty () || (name_template == name_pattern);
while (how_many) {
if (!find_route_name (name_template.empty() ? _(name_pattern.c_str()) : name_template, ++track_id, track_name, use_number)) {
error << "cannot find name for new audio track" << endmsg;
goto failed;
}
boost::shared_ptr<AudioTrack> track;
try {
track.reset (new AudioTrack (*this, track_name, mode));
if (track->init ()) {
goto failed;
}
if (Profile->get_mixbus ()) {
track->set_strict_io (true);
}
BOOST_MARK_TRACK (track);
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
if (track->input()->ensure_io (ChanCount(DataType::AUDIO, input_channels), false, this)) {
error << string_compose (
_("cannot configure %1 in/%2 out configuration for new audio track"),
input_channels, output_channels)
<< endmsg;
goto failed;
}
if (track->output()->ensure_io (ChanCount(DataType::AUDIO, output_channels), false, this)) {
error << string_compose (
_("cannot configure %1 in/%2 out configuration for new audio track"),
input_channels, output_channels)
<< endmsg;
goto failed;
}
}
if (route_group) {
route_group->add (track);
}
track->presentation_info ().set_trigger_track (trigger_visibility);
new_routes.push_back (track);
ret.push_back (track);
}
catch (failed_constructor &err) {
error << _("Session: could not create new audio track.") << endmsg;
goto failed;
}
catch (AudioEngine::PortRegistrationFailure& pfe) {
error << pfe.what() << endmsg;
goto failed;
}
--how_many;
}
failed:
if (!new_routes.empty()) {
add_routes (new_routes, input_auto_connect, true, order);
}
return ret;
}
/** Caller must not hold process lock.
* @param name_template string to use for the start of the name, or "" to use "Bus".
*/
RouteList
Session::new_audio_route (int input_channels, int output_channels, RouteGroup* route_group, uint32_t how_many, string name_template,
PresentationInfo::Flag flags, PresentationInfo::order_t order)
{
string bus_name;
uint32_t bus_id = 0;
string port;
RouteList ret;
bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Bus");
while (how_many) {
if (!find_route_name (name_template.empty () ? _("Bus") : name_template, ++bus_id, bus_name, use_number)) {
error << "cannot find name for new audio bus" << endmsg;
goto failure;
}
try {
boost::shared_ptr<Route> bus (new Route (*this, bus_name, flags, DataType::AUDIO));
if (bus->init ()) {
goto failure;
}
if (Profile->get_mixbus ()) {
bus->set_strict_io (true);
}
BOOST_MARK_ROUTE(bus);
{
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
if (bus->input()->ensure_io (ChanCount(DataType::AUDIO, input_channels), false, this)) {
error << string_compose (_("cannot configure %1 in/%2 out configuration for new audio track"),
input_channels, output_channels)
<< endmsg;
goto failure;
}
if (bus->output()->ensure_io (ChanCount(DataType::AUDIO, output_channels), false, this)) {
error << string_compose (_("cannot configure %1 in/%2 out configuration for new audio track"),
input_channels, output_channels)
<< endmsg;
goto failure;
}
}
if (route_group) {
route_group->add (bus);
}
bus->add_internal_return ();
ret.push_back (bus);
}
catch (failed_constructor &err) {
error << _("Session: could not create new audio bus.") << endmsg;
goto failure;
}
catch (AudioEngine::PortRegistrationFailure& pfe) {
error << pfe.what() << endmsg;
goto failure;
}
--how_many;
}
failure:
if (!ret.empty()) {
if (flags == PresentationInfo::FoldbackBus) {
add_routes (ret, false, false, order); // no autoconnect
} else {
add_routes (ret, false, true, order); // autoconnect // outputs only
}
}
return ret;
}
RouteList
Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t insert_at, const std::string& template_path, const std::string& name_base,
PlaylistDisposition pd)
{
XMLTree tree;
if (!tree.read (template_path.c_str())) {
return RouteList();
}
return new_route_from_template (how_many, insert_at, *tree.root(), name_base, pd);
}
RouteList
Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t insert_at, XMLNode& node, const std::string& name_base, PlaylistDisposition pd)
{
RouteList ret;
uint32_t number = 0;
const uint32_t being_added = how_many;
/* This will prevent the use of any existing XML-provided PBD::ID
values by Stateful.
*/
Stateful::ForceIDRegeneration force_ids;
/* New v6 templates do have a version in the Route-Template,
* we assume that all older, unversioned templates are
* from Ardour 5.x
* when Stateful::loading_state_version was 3002
*/
int version = 3002;
node.get_property (X_("version"), version);
while (how_many) {
/* We're going to modify the node contents a bit so take a
* copy. The node may be re-used when duplicating more than once.
*/
XMLNode node_copy (node);
std::vector<boost::shared_ptr<Playlist> > shared_playlists;
try {
string name;
if (!name_base.empty()) {
/* if we're adding more than one routes, force
* all the names of the new routes to be
* numbered, via the final parameter.
*/
if (!find_route_name (name_base.c_str(), ++number, name, (being_added > 1))) {
fatal << _("Session: Failed to create unique ID for track from template.") << endmsg;
abort(); /*NOTREACHED*/
}
} else {
string const route_name = node_copy.property(X_("name"))->value ();
/* generate a new name by adding a number to the end of the template name */
if (!find_route_name (route_name, ++number, name, true)) {
fatal << _("Session: Failed to generate unique name and ID for track from template.") << endmsg;
abort(); /*NOTREACHED*/
}
}
/* 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) {
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) {
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;
std::string default_type;
node.get_property(X_("default-type"), default_type);
if (node_copy.get_property (X_("audio-playlist"), pid) || (version < 5000 && default_type == "audio")) {
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) || (version < 5000 && default_type == "midi")) {
boost::shared_ptr<Playlist> playlist = PlaylistFactory::create (DataType::MIDI, *this, name, false);
node_copy.set_property (X_("midi-playlist"), playlist->id());
}
}
/* Fix up new name in the XML node */
Route::set_name_in_state (node_copy, name);
/* trim bitslots from listen sends so that new ones are used */
XMLNodeList children = node_copy.children ();
for (XMLNodeList::iterator i = children.begin(); i != children.end(); ++i) {
if ((*i)->name() == X_("Processor")) {
/* ForceIDRegeneration does not catch the following */
XMLProperty const * role = (*i)->property (X_("role"));
XMLProperty const * type = (*i)->property (X_("type"));
if (role && role->value() == X_("Aux")) {
/* Check if the target bus exists.
* This is mainly useful when duplicating tracks
* (aux-sends should not be saved in templates).
*/
XMLProperty const * target = (*i)->property (X_("target"));
if (!target) {
(*i)->set_property ("type", "dangling-aux-send");
continue;
}
boost::shared_ptr<Route> r = route_by_id (target->value());
if (!r || boost::dynamic_pointer_cast<Track>(r)) {
(*i)->set_property ("type", "dangling-aux-send");
continue;
}
}
if (role && role->value() == X_("Listen")) {
(*i)->remove_property (X_("bitslot"));
}
else if (role && (role->value() == X_("Send") || role->value() == X_("Aux"))) {
Delivery::Role xrole;
uint32_t bitslot = 0;
xrole = Delivery::Role (string_2_enum (role->value(), xrole));
/* generate new bitslot ID */
std::string name = Send::name_and_id_new_send(*this, xrole, bitslot, false);
(*i)->remove_property (X_("bitslot"));
(*i)->set_property ("bitslot", bitslot);
/* external sends need unique names */
if (role->value() == X_("Send")) {
(*i)->remove_property (X_("name"));
(*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)->set_property ("ignore-bitslot", "1");
}
else if (type && type->value() == X_("return")) {
// Return::set_state() generates a new one
(*i)->remove_property (X_("bitslot"));
}
else if (type && type->value() == X_("port")) {
IOProcessor::prepare_for_reset (**i, name);
}
}
}
/* new routes start off unsoloed to avoid issues related to
upstream / downstream buses.
*/
node_copy.remove_node_and_delete (X_("Controllable"), X_("name"), X_("solo"));
boost::shared_ptr<Route> route;
if (version < 3000) {
route = XMLRouteFactory_2X (node_copy, version);
} else if (version < 5000) {
route = XMLRouteFactory_3X (node_copy, version);
} else {
route = XMLRouteFactory (node_copy, version);
}
if (route == 0) {
error << _("Session: cannot create track/bus from template description") << endmsg;
goto out;
}
{
PresentationInfo& rpi = route->presentation_info ();
rpi.set_flags (PresentationInfo::Flag (rpi.flags() & ~PresentationInfo::OrderSet));
}
/* 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
loading this normally happens in a different way.
*/
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
IOChange change (IOChange::Type (IOChange::ConfigurationChanged | IOChange::ConnectionsChanged));
change.after = route->input()->n_ports();
route->input()->changed (change, this);
change.after = route->output()->n_ports();
route->output()->changed (change, this);
}
ret.push_back (route);
}
catch (failed_constructor &err) {
error << _("Session: could not create new track/bus from template") << endmsg;
goto out;
}
catch (AudioEngine::PortRegistrationFailure& pfe) {
error << pfe.what() << endmsg;
goto out;
}
catch (...) {
throw;
}
--how_many;
}
out:
if (!ret.empty()) {
add_routes (ret, false, false, insert_at);
}
if (!ret.empty()) {
/* set/unset monitor-send */
Glib::Threads::Mutex::Lock lm (_engine.process_lock());
for (RouteList::iterator x = ret.begin(); x != ret.end(); ++x) {
if ((*x)->can_monitor ()) {
if (_monitor_out) {
(*x)->enable_monitor_send ();
} else {
/* this may happen with old templates */
(*x)->remove_monitor_send ();
}
}
/* reconnect ports using information from state */
for (auto const& wio : (*x)->all_inputs ()) {
boost::shared_ptr<IO> io = wio.lock();
if (!io) {
continue;
}
for (auto const& p : io->ports()) {
p->reconnect ();
}
}
for (auto const& wio : (*x)->all_outputs ()) {
boost::shared_ptr<IO> io = wio.lock();
if (!io) {
continue;
}
for (auto const& p : io->ports()) {
p->reconnect ();
}
}
}
}
return ret;
}
void
Session::add_routes (RouteList& new_routes, bool input_auto_connect, bool output_auto_connect, PresentationInfo::order_t order)
{
try {
PBD::Unwinder<bool> aip (_adding_routes_in_progress, true);
add_routes_inner (new_routes, input_auto_connect, output_auto_connect, order);
} catch (...) {
error << _("Adding new tracks/busses failed") << endmsg;
}
/* During the route additions there will have been potentially several
* signals emitted to indicate the new graph. ::graph_reordered() will
* have ignored all of them because _adding_routes_in_progress was
* true.
*
* We still need the effects of ::graph_reordered(), but we didn't want
* it called multiple times during the addition of multiple routes. Now
* that the addition is done, call it explicitly.
*/
graph_reordered (false);
set_dirty();
update_route_record_state ();
RouteAdded (new_routes); /* EMIT SIGNAL */
}
void
Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool output_auto_connect, PresentationInfo::order_t order)
{
ChanCount existing_inputs;
ChanCount existing_outputs;
uint32_t n_routes;
uint32_t added = 0;
count_existing_track_channels (existing_inputs, existing_outputs);
{
RCUWriter<RouteList> writer (routes);
boost::shared_ptr<RouteList> r = writer.get_copy ();
n_routes = r->size();
r->insert (r->end(), new_routes.begin(), new_routes.end());
/* if there is no control out and we're not in the middle of loading,
* resort the graph here. if there is a control out, we will resort
* toward the end of this method. if we are in the middle of loading,
* we will resort when done.
*/
if (!_monitor_out && !loading() && !input_auto_connect && !output_auto_connect) {
resort_routes_using (r);
}
}
/* monitor is not part of the order */
if (_monitor_out) {
assert (n_routes > 0);
--n_routes;
}
{
PresentationInfo::ChangeSuspender cs;
ensure_route_presentation_info_gap (order, new_routes.size());
for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x, ++added) {
boost::weak_ptr<Route> wpr (*x);
boost::shared_ptr<Route> r (*x);
r->solo_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _2,wpr));
r->solo_isolate_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, wpr));
r->mute_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this));
r->processors_changed.connect_same_thread (*this, boost::bind (&Session::route_processors_changed, this, _1));
r->processor_latency_changed.connect_same_thread (*this, boost::bind (&Session::queue_latency_recompute, this));
if (r->is_master()) {
_master_out = r;
}
if (r->is_monitor()) {
_monitor_out = r;
}
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (r);
if (tr) {
tr->PlaylistChanged.connect_same_thread (*this, boost::bind (&Session::track_playlist_changed, this, boost::weak_ptr<Track> (tr)));
track_playlist_changed (boost::weak_ptr<Track> (tr));
tr->rec_enable_control()->Changed.connect_same_thread (*this, boost::bind (&Session::update_route_record_state, this));
boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (tr);
if (mt) {
mt->StepEditStatusChange.connect_same_thread (*this, boost::bind (&Session::step_edit_status_change, this, _1));
mt->presentation_info().PropertyChanged.connect_same_thread (*this, boost::bind (&Session::midi_track_presentation_info_changed, this, _1, boost::weak_ptr<MidiTrack>(mt)));
}
}
if (r->triggerbox()) {
r->triggerbox()->EmptyStatusChanged.connect_same_thread (*this, boost::bind (&Session::handle_slots_empty_status, this, wpr));
if (!r->triggerbox()->empty()) {
tb_with_filled_slots++;
}
}
if (!r->presentation_info().special (false)) {
DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("checking PI state for %1\n", r->name()));
/* presentation info order may already have been set from XML */
if (!r->presentation_info().order_set()) {
if (order == PresentationInfo::max_order) {
/* just add to the end */
r->set_presentation_order (n_routes + added);
DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to NR %1 + %2 = %3\n", n_routes, added, n_routes + added));
} else {
r->set_presentation_order (order + added);
DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to %1 + %2 = %3\n", order, added, order + added));
}
} else {
DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order already set to %1\n", r->presentation_info().order()));
}
}
DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("added route %1, pi %2\n", r->name(), r->presentation_info()));
if (input_auto_connect || output_auto_connect) {
auto_connect_route (r, input_auto_connect, output_auto_connect, ChanCount (), ChanCount (), existing_inputs, existing_outputs);
if (input_auto_connect) {
existing_inputs += r->n_inputs();
}
if (output_auto_connect) {
existing_outputs += r->n_outputs();
}
}
ARDOUR::GUIIdle ();
}
ensure_stripable_sort_order ();
}
if (_monitor_out && !loading()) {
Glib::Threads::Mutex::Lock lm (_engine.process_lock());
for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x) {
if ((*x)->can_monitor ()) {
(*x)->enable_monitor_send ();
}
}
}
reassign_track_numbers ();
}
void
Session::load_and_connect_instruments (RouteList& new_routes, bool strict_io, boost::shared_ptr<PluginInfo> instrument, Plugin::PresetRecord* pset, ChanCount& existing_outputs)
{
if (instrument) {
for (RouteList::iterator r = new_routes.begin(); r != new_routes.end(); ++r) {
PluginPtr plugin = instrument->load (*this);
if (!plugin) {
warning << "Failed to add Synth Plugin to newly created track." << endmsg;
continue;
}
if (pset) {
plugin->load_preset (*pset);
}
boost::shared_ptr<PluginInsert> pi (new PluginInsert (*this, (*r)->time_domain(), plugin));
if (strict_io) {
pi->set_strict_io (true);
}
(*r)->add_processor (pi, PreFader);
if (Profile->get_mixbus () && pi->configured () && pi->output_streams().n_audio() > 2) {
(*r)->move_instrument_down (false);
}
/* Route::add_processors -> Delivery::configure_io -> IO::ensure_ports
* should have registered the ports, so now we can call.. */
if (!(*r)->instrument_fanned_out ()) {
auto_connect_route (*r, false, true, ChanCount (), ChanCount (), ChanCount (), existing_outputs);
existing_outputs += (*r)->n_outputs();
}
}
}
for (RouteList::iterator r = new_routes.begin(); r != new_routes.end(); ++r) {
(*r)->output()->changed.connect_same_thread (*this, boost::bind (&Session::midi_output_change_handler, this, _1, _2, boost::weak_ptr<Route>(*r)));
}
}
void
Session::globally_set_send_gains_to_zero (boost::shared_ptr<Route> dest)
{
boost::shared_ptr<RouteList> r = routes.reader ();
boost::shared_ptr<Send> s;
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((s = (*i)->internal_send_for (dest)) != 0) {
s->gain_control()->set_value (GAIN_COEFF_ZERO, Controllable::NoGroup);
}
}
}
void
Session::globally_set_send_gains_to_unity (boost::shared_ptr<Route> dest)
{
boost::shared_ptr<RouteList> r = routes.reader ();
boost::shared_ptr<Send> s;
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((s = (*i)->internal_send_for (dest)) != 0) {
s->gain_control()->set_value (GAIN_COEFF_UNITY, Controllable::NoGroup);
}
}
}
void
Session::globally_set_send_gains_from_track(boost::shared_ptr<Route> dest)
{
boost::shared_ptr<RouteList> r = routes.reader ();
boost::shared_ptr<Send> s;
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((s = (*i)->internal_send_for (dest)) != 0) {
s->gain_control()->set_value ((*i)->gain_control()->get_value(), Controllable::NoGroup);
}
}
}
/** @param include_buses true to add sends to buses and tracks, false for just tracks */
void
Session::globally_add_internal_sends (boost::shared_ptr<Route> dest, Placement p, bool include_buses)
{
boost::shared_ptr<RouteList> r = routes.reader ();
boost::shared_ptr<RouteList> t (new RouteList);
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
/* no MIDI sends because there are no MIDI busses yet */
if (include_buses || boost::dynamic_pointer_cast<AudioTrack>(*i)) {
t->push_back (*i);
}
}
add_internal_sends (dest, p, t);
}
void
Session::add_internal_sends (boost::shared_ptr<Route> dest, Placement p, boost::shared_ptr<RouteList> senders)
{
for (RouteList::iterator i = senders->begin(); i != senders->end(); ++i) {
add_internal_send (dest, (*i)->before_processor_for_placement (p), *i);
}
}
void
Session::add_internal_send (boost::shared_ptr<Route> dest, int index, boost::shared_ptr<Route> sender)
{
add_internal_send (dest, sender->before_processor_for_index (index), sender);
}
void
Session::add_internal_send (boost::shared_ptr<Route> dest, boost::shared_ptr<Processor> before, boost::shared_ptr<Route> sender)
{
if (sender->is_monitor() || sender->is_master() || sender == dest || dest->is_monitor() || dest->is_master()) {
return;
}
if (!dest->internal_return()) {
dest->add_internal_return ();
}
sender->add_aux_send (dest, before);
graph_reordered (false);
}
void
Session::remove_routes (boost::shared_ptr<RouteList> routes_to_remove)
{
bool mute_changed = false;
bool send_selected = false;
{ // RCU Writer scope
PBD::Unwinder<bool> uw_flag (_route_deletion_in_progress, true);
RCUWriter<RouteList> writer (routes);
boost::shared_ptr<RouteList> rs = writer.get_copy ();
for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) {
if (_selection->selected (*iter)) {
send_selected = true;
}
if (*iter == _master_out) {
continue;
}
/* speed up session deletion, don't do the solo dance */
if (!deletion_in_progress ()) {
(*iter)->solo_control()->set_value (0.0, Controllable::NoGroup);
}
if ((*iter)->mute_control()->muted ()) {
mute_changed = true;
}
rs->remove (*iter);
/* deleting the master out seems like a dumb
idea, but its more of a UI policy issue
than our concern.
*/
if (*iter == _master_out) {
_master_out = boost::shared_ptr<Route> ();
}
if (*iter == _monitor_out) {
_monitor_out.reset ();
}
// We need to disconnect the route's inputs and outputs
(*iter)->input()->disconnect (0);
(*iter)->output()->disconnect (0);
/* if the route had internal sends sending to it, remove them */
if (!deletion_in_progress () && (*iter)->internal_return()) {
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
boost::shared_ptr<Send> s = (*i)->internal_send_for (*iter);
if (s) {
(*i)->remove_processor (s);
}
}
}
/* if the monitoring section had a pointer to this route, remove it */
if (!deletion_in_progress () && _monitor_out && (*iter)->can_monitor ()) {
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
ProcessorChangeBlocker pcb (this, false);
(*iter)->remove_monitor_send ();
}
boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (*iter);
if (mt && mt->step_editing()) {
if (_step_editors > 0) {
_step_editors--;
}
}
}
/* writer goes out of scope, forces route list update */
} // end of RCU Writer scope
if (mute_changed) {
MuteChanged (); /* EMIT SIGNAL */
}
update_route_solo_state ();
update_latency_compensation (false, false);
set_dirty();
/* Re-sort routes to remove the graph's current references to the one that is
* going away, then flush old references out of the graph.
*/
routes.flush (); // maybe unsafe, see below.
resort_routes ();
/* get rid of it from the dead wood collection in the route list manager */
/* XXX i think this is unsafe as it currently stands, but i am not sure. (pd, october 2nd, 2006) */
routes.flush ();
/* remove these routes from the selection if appropriate, and signal
* the change *before* we call DropReferences for them.
*/
if (send_selected && !deletion_in_progress()) {
for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) {
_selection->remove_stripable_by_id ((*iter)->id());
}
PropertyChange pc;
pc.add (Properties::selected);
PresentationInfo::Change (pc);
}
/* try to cause everyone to drop their references
* and unregister ports from the backend
*/
for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) {
(*iter)->drop_references ();
}
if (deletion_in_progress()) {
return;
}
PropertyChange pc;
pc.add (Properties::order);
PresentationInfo::Change (pc);
update_route_record_state ();
}
void
Session::remove_route (boost::shared_ptr<Route> route)
{
boost::shared_ptr<RouteList> rl (new RouteList);
rl->push_back (route);
remove_routes (rl);
}
void
Session::route_mute_changed ()
{
MuteChanged (); /* EMIT SIGNAL */
set_dirty ();
}
void
Session::route_listen_changed (Controllable::GroupControlDisposition group_override, boost::weak_ptr<Route> wpr)
{
boost::shared_ptr<Route> route (wpr.lock());
if (!route) {
return;
}
assert (Config->get_solo_control_is_listen_control());
if (route->solo_control()->soloed_by_self_or_masters()) {
if (Config->get_exclusive_solo()) {
_engine.monitor_port().clear_ports (false);
RouteGroup* rg = route->route_group ();
const bool group_already_accounted_for = (group_override == Controllable::ForGroup);
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((*i) == route) {
/* already changed */
continue;
}
if ((*i)->solo_isolate_control()->solo_isolated() || !(*i)->can_monitor()) {
/* route does not get solo propagated to it */
continue;
}
if ((group_already_accounted_for && (*i)->route_group() && (*i)->route_group() == rg)) {
/* this route is a part of the same solo group as the route
* that was changed. Changing that route did change or will
* change all group members appropriately, so we can ignore it
* here
*/
continue;
}
(*i)->solo_control()->set_value (0.0, Controllable::NoGroup);
}
}
_listen_cnt++;
} else if (_listen_cnt > 0) {
_listen_cnt--;
}
}
void
Session::route_solo_isolated_changed (boost::weak_ptr<Route> wpr)
{
boost::shared_ptr<Route> route (wpr.lock());
if (!route) {
return;
}
bool send_changed = false;
if (route->solo_isolate_control()->solo_isolated()) {
if (_solo_isolated_cnt == 0) {
send_changed = true;
}
_solo_isolated_cnt++;
} else if (_solo_isolated_cnt > 0) {
_solo_isolated_cnt--;
if (_solo_isolated_cnt == 0) {
send_changed = true;
}
}
if (send_changed) {
IsolatedChanged (); /* EMIT SIGNAL */
}
}
void
Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlDisposition group_override, boost::weak_ptr<Route> wpr)
{
DEBUG_TRACE (DEBUG::Solo, string_compose ("route solo change, self = %1, update\n", self_solo_changed));
boost::shared_ptr<Route> route (wpr.lock());
if (!route) {
return;
}
if (Config->get_solo_control_is_listen_control()) {
route_listen_changed (group_override, wpr);
return;
}
DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: self %2 masters %3 transition %4\n", route->name(), route->self_soloed(), route->solo_control()->get_masters_value(), route->solo_control()->transitioned_into_solo()));
if (route->solo_control()->transitioned_into_solo() == 0) {
/* route solo changed by upstream/downstream or clear all solo state; not interesting
to Session.
*/
DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 not self-soloed nor soloed by master (%2), ignoring\n", route->name(), route->solo_control()->get_masters_value()));
return;
}
boost::shared_ptr<RouteList> r = routes.reader ();
int32_t delta = route->solo_control()->transitioned_into_solo ();
/* the route may be a member of a group that has shared-solo
* semantics. If so, then all members of that group should follow the
* solo of the changed route. But ... this is optional, controlled by a
* Controllable::GroupControlDisposition.
*
* The first argument to the signal that this method is connected to is the
* GroupControlDisposition value that was used to change solo.
*
* If the solo change was done with group semantics (either InverseGroup
* (force the entire group to change even if the group shared solo is
* disabled) or UseGroup (use the group, which may or may not have the
* shared solo property enabled)) then as we propagate the change to
* the entire session we should IGNORE THE GROUP that the changed route
* belongs to.
*/
RouteGroup* rg = route->route_group ();
const bool group_already_accounted_for = (group_override == Controllable::ForGroup);
DEBUG_TRACE (DEBUG::Solo, string_compose ("propagate to session, group accounted for ? %1\n", group_already_accounted_for));
if (delta == 1 && Config->get_exclusive_solo()) {
/* new solo: disable all other solos, but not the group if its solo-enabled */
_engine.monitor_port().clear_ports (false);
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((*i) == route) {
/* already changed */
continue;
}
if ((*i)->solo_isolate_control()->solo_isolated() || !(*i)->can_solo()) {
/* route does not get solo propagated to it */
continue;
}
if ((group_already_accounted_for && (*i)->route_group() && (*i)->route_group() == rg)) {
/* this route is a part of the same solo group as the route
* that was changed. Changing that route did change or will
* change all group members appropriately, so we can ignore it
* here
*/
continue;
}
(*i)->solo_control()->set_value (0.0, group_override);
}
}
DEBUG_TRACE (DEBUG::Solo, string_compose ("propagate solo change, delta = %1\n", delta));
RouteList uninvolved;
DEBUG_TRACE (DEBUG::Solo, string_compose ("%1\n", route->name()));
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
bool in_signal_flow;
if ((*i) == route) {
/* already changed */
continue;
}
if ((*i)->solo_isolate_control()->solo_isolated() || !(*i)->can_solo()) {
/* route does not get solo propagated to it */
DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 excluded from solo because iso = %2 can_solo = %3\n", (*i)->name(), (*i)->solo_isolate_control()->solo_isolated(),
(*i)->can_solo()));
continue;
}
if ((group_already_accounted_for && (*i)->route_group() && (*i)->route_group() == rg)) {
/* this route is a part of the same solo group as the route
* that was changed. Changing that route did change or will
* change all group members appropriately, so we can ignore it
* here
*/
continue;
}
in_signal_flow = false;
DEBUG_TRACE (DEBUG::Solo, string_compose ("check feed from %1\n", (*i)->name()));
if ((*i)->feeds (route)) {
DEBUG_TRACE (DEBUG::Solo, string_compose ("\tthere is a feed from %1\n", (*i)->name()));
if (!route->soloed_by_others_upstream()) {
(*i)->solo_control()->mod_solo_by_others_downstream (delta);
} else {
DEBUG_TRACE (DEBUG::Solo, "\talready soloed by others upstream\n");
}
in_signal_flow = true;
} else {
DEBUG_TRACE (DEBUG::Solo, string_compose ("\tno feed from %1\n", (*i)->name()));
}
DEBUG_TRACE (DEBUG::Solo, string_compose ("check feed to %1\n", (*i)->name()));
if (route->feeds (*i)) {
DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 feeds %2 sboD %3 sboU %4\n",
route->name(),
(*i)->name(),
route->soloed_by_others_downstream(),
route->soloed_by_others_upstream()));
//NB. Triggers Invert Push, which handles soloed by downstream
DEBUG_TRACE (DEBUG::Solo, string_compose ("\tmod %1 by %2\n", (*i)->name(), delta));
(*i)->solo_control()->mod_solo_by_others_upstream (delta);
in_signal_flow = true;
} else {
DEBUG_TRACE (DEBUG::Solo, string_compose("\tno feed to %1\n", (*i)->name()) );
}
if (!in_signal_flow) {
uninvolved.push_back (*i);
}
}
DEBUG_TRACE (DEBUG::Solo, "propagation complete\n");
/* now notify that the mute state of the routes not involved in the signal
pathway of the just-solo-changed route may have altered.
*/
for (RouteList::iterator i = uninvolved.begin(); i != uninvolved.end(); ++i) {
DEBUG_TRACE (DEBUG::Solo, string_compose ("mute change for %1, which neither feeds or is fed by %2\n", (*i)->name(), route->name()));
(*i)->act_on_mute ();
/* Session will emit SoloChanged() after all solo changes are
* complete, which should be used by UIs to update mute status
*/
}
}
void
Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
{
/* now figure out if anything that matters is soloed (or is "listening")*/
bool something_soloed = false;
bool something_listening = false;
uint32_t listeners = 0;
uint32_t isolated = 0;
if (!r) {
r = routes.reader();
}
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((*i)->can_monitor() && Config->get_solo_control_is_listen_control()) {
if ((*i)->solo_control()->soloed_by_self_or_masters()) {
listeners++;
something_listening = true;
}
} else if ((*i)->can_solo()) {
(*i)->set_listen (false);
if ((*i)->can_solo() && (*i)->solo_control()->soloed_by_self_or_masters()) {
something_soloed = true;
}
}
if ((*i)->solo_isolate_control()->solo_isolated()) {
isolated++;
}
}
if (something_soloed != _non_soloed_outs_muted) {
_non_soloed_outs_muted = something_soloed;
SoloActive (_non_soloed_outs_muted); /* EMIT SIGNAL */
}
if (something_listening != _listening) {
_listening = something_listening;
SoloActive (_listening);
}
_listen_cnt = listeners;
if (isolated != _solo_isolated_cnt) {
_solo_isolated_cnt = isolated;
IsolatedChanged (); /* EMIT SIGNAL */
}
DEBUG_TRACE (DEBUG::Solo, string_compose ("solo state updated by session, soloed? %1 listeners %2 isolated %3\n",
something_soloed, listeners, isolated));
SoloChanged (); /* EMIT SIGNAL */
set_dirty();
}
bool
Session::muted () const
{
// TODO consider caching the value on every MuteChanged signal,
// Note that API users may also subscribe to MuteChanged and hence
// this method needs to be called first.
bool muted = false;
StripableList all;
get_stripables (all);
for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) {
assert (!(*i)->is_auditioner()); // XXX remove me
if ((*i)->is_monitor()) {
continue;
}
boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route>(*i);
if (r && !r->active()) {
continue;
}
boost::shared_ptr<MuteControl> mc = (*i)->mute_control();
if (mc && mc->muted ()) {
muted = true;
break;
}
}
return muted;
}
std::vector<boost::weak_ptr<AutomationControl> >
Session::cancel_all_mute ()
{
StripableList all;
get_stripables (all);
std::vector<boost::weak_ptr<AutomationControl> > muted;
boost::shared_ptr<ControlList> cl (new ControlList);
for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) {
assert (!(*i)->is_auditioner());
if ((*i)->is_monitor()) {
continue;
}
boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (*i);
if (r && !r->active()) {
continue;
}
boost::shared_ptr<AutomationControl> ac = (*i)->mute_control();
if (ac && ac->get_value () > 0) {
cl->push_back (ac);
muted.push_back (boost::weak_ptr<AutomationControl>(ac));
}
}
if (!cl->empty ()) {
set_controls (cl, 0.0, PBD::Controllable::UseGroup);
}
return muted;
}
void
Session::get_stripables (StripableList& sl, PresentationInfo::Flag fl) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator it = r->begin(); it != r->end(); ++it) {
if ((*it)->presentation_info ().flags () & fl) {
sl.push_back (*it);
}
}
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, 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
{
boost::shared_ptr<RouteList> r = routes.reader ();
boost::shared_ptr<RouteList> rl (new RouteList);
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((*i)->internal_return ()) {
rl->push_back (*i);
}
}
return rl;
}
bool
Session::io_name_is_legal (const std::string& name) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (map<string,bool>::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) {
if (name == reserved->first) {
if (!route_by_name (reserved->first)) {
/* first instance of a reserved name is allowed for some */
return reserved->second;
}
/* all other instances of a reserved name are not allowed */
return false;
}
}
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((*i)->name() == name) {
return false;
}
if ((*i)->has_io_processor_named (name)) {
return false;
}
}
boost::shared_ptr<IOPlugList> iop (_io_plugins.reader ());
for (auto const& i : *iop) {
if (i->io_name () == name) {
return false;
}
}
return true;
}
void
Session::set_exclusive_input_active (boost::shared_ptr<RouteList> rl, bool onoff, bool flip_others)
{
RouteList rl2;
vector<string> connections;
/* if we are passed only a single route and we're not told to turn
* others off, then just do the simple thing.
*/
if (flip_others == false && rl->size() == 1) {
boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (rl->front());
if (mt) {
mt->set_input_active (onoff);
return;
}
}
for (RouteList::iterator rt = rl->begin(); rt != rl->end(); ++rt) {
PortSet& ps ((*rt)->input()->ports());
for (PortSet::iterator p = ps.begin(); p != ps.end(); ++p) {
p->get_connections (connections);
}
for (vector<string>::iterator s = connections.begin(); s != connections.end(); ++s) {
routes_using_input_from (*s, rl2);
}
/* scan all relevant routes to see if others are on or off */
bool others_are_already_on = false;
for (RouteList::iterator r = rl2.begin(); r != rl2.end(); ++r) {
boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (*r);
if (!mt) {
continue;
}
if ((*r) != (*rt)) {
if (mt->input_active()) {
others_are_already_on = true;
}
} else {
/* this one needs changing */
mt->set_input_active (onoff);
}
}
if (flip_others) {
/* globally reverse other routes */
for (RouteList::iterator r = rl2.begin(); r != rl2.end(); ++r) {
if ((*r) != (*rt)) {
boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (*r);
if (mt) {
mt->set_input_active (!others_are_already_on);
}
}
}
}
}
}
void
Session::routes_using_input_from (const string& str, RouteList& rl)
{
boost::shared_ptr<RouteList> r = routes.reader();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((*i)->input()->connected_to (str)) {
rl.push_back (*i);
}
}
}
boost::shared_ptr<Route>
Session::route_by_name (string name) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((*i)->name() == name) {
return *i;
}
}
return boost::shared_ptr<Route> ((Route*) 0);
}
boost::shared_ptr<Route>
Session::route_by_id (PBD::ID id) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((*i)->id() == id) {
return *i;
}
}
return boost::shared_ptr<Route> ((Route*) 0);
}
boost::shared_ptr<Stripable>
Session::stripable_by_id (PBD::ID id) const
{
StripableList sl;
get_stripables (sl);
for (StripableList::const_iterator s = sl.begin(); s != sl.end(); ++s) {
if ((*s)->id() == id) {
return *s;
}
}
return boost::shared_ptr<Stripable>();
}
boost::shared_ptr<Trigger>
Session::trigger_by_id (PBD::ID id) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
boost::shared_ptr<TriggerBox> box = (*i)->triggerbox();
if (box) {
TriggerPtr trigger = box->trigger_by_id(id);
if (trigger) {
return trigger;
}
}
}
return boost::shared_ptr<Trigger> ();
}
boost::shared_ptr<Processor>
Session::processor_by_id (PBD::ID id) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
boost::shared_ptr<Processor> p = (*i)->Route::processor_by_id (id);
if (p) {
return p;
}
}
return boost::shared_ptr<Processor> ();
}
boost::shared_ptr<Route>
Session::get_remote_nth_route (PresentationInfo::order_t n) const
{
return boost::dynamic_pointer_cast<Route> (get_remote_nth_stripable (n, PresentationInfo::Route));
}
boost::shared_ptr<Stripable>
Session::get_remote_nth_stripable (PresentationInfo::order_t n, PresentationInfo::Flag flags) const
{
StripableList sl;
PresentationInfo::order_t match_cnt = 0;
get_stripables (sl);
sl.sort (Stripable::Sorter());
for (StripableList::const_iterator s = sl.begin(); s != sl.end(); ++s) {
if ((*s)->presentation_info().hidden()) {
/* if the caller didn't explicitly ask for hidden
stripables, ignore hidden ones. This matches
the semantics of the pre-PresentationOrder
"get by RID" logic of Ardour 4.x and earlier.
XXX at some point we should likely reverse
the logic of the flags, because asking for "the
hidden stripables" is not going to be common,
whereas asking for visible ones is normal.
*/
if (! (flags & PresentationInfo::Hidden)) {
continue;
}
}
if ((*s)->presentation_info().flag_match (flags)) {
if (match_cnt++ == n) {
return *s;
}
}
}
/* there is no nth stripable that matches the given flags */
return boost::shared_ptr<Stripable>();
}
boost::shared_ptr<Route>
Session::route_by_selected_count (uint32_t id) const
{
RouteList r (*(routes.reader ()));
r.sort (Stripable::Sorter());
RouteList::iterator i;
for (i = r.begin(); i != r.end(); ++i) {
if ((*i)->is_selected()) {
if (id == 0) {
return *i;
}
--id;
}
}
return boost::shared_ptr<Route> ();
}
void
Session::reassign_track_numbers ()
{
int64_t tn = 0;
int64_t bn = 0;
uint32_t trigger_order = 0;
RouteList r (*(routes.reader ()));
r.sort (Stripable::Sorter());
StateProtector sp (this);
for (RouteList::iterator i = r.begin(); i != r.end(); ++i) {
assert (!(*i)->is_auditioner());
if (boost::dynamic_pointer_cast<Track> (*i)) {
(*i)->set_track_number(++tn);
} else if (!(*i)->is_master() && !(*i)->is_monitor()) {
(*i)->set_track_number(--bn);
}
boost::shared_ptr<TriggerBox> tb = (*i)->triggerbox();
if (tb) {
tb->set_order (trigger_order);
trigger_order++;
}
}
const uint32_t decimals = ceilf (log10f (tn + 1));
const bool decimals_changed = _track_number_decimals != decimals;
_track_number_decimals = decimals;
if (decimals_changed && config.get_track_name_number ()) {
for (RouteList::iterator i = r.begin(); i != r.end(); ++i) {
boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (*i);
if (t) {
t->resync_take_name ();
}
}
// trigger GUI re-layout
config.ParameterChanged("track-name-number");
}
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::OrderKeys)) {
boost::shared_ptr<RouteList> rl = routes.reader ();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("%1 numbered %2\n", (*i)->name(), (*i)->track_number()));
}
}
#endif /* NDEBUG */
}
void
Session::playlist_region_added (boost::weak_ptr<Region> w)
{
boost::shared_ptr<Region> r = w.lock ();
if (!r) {
return;
}
/* These are the operations that are currently in progress... */
list<GQuark> curr = _current_trans_quarks;
curr.sort ();
/* ...and these are the operations during which we want to update
the session range location markers.
*/
list<GQuark> ops;
ops.push_back (Operations::capture);
ops.push_back (Operations::paste);
ops.push_back (Operations::duplicate_region);
ops.push_back (Operations::insert_file);
ops.push_back (Operations::insert_region);
ops.push_back (Operations::drag_region_brush);
ops.push_back (Operations::region_drag);
ops.push_back (Operations::selection_grab);
ops.push_back (Operations::region_fill);
ops.push_back (Operations::fill_selection);
ops.push_back (Operations::create_region);
ops.push_back (Operations::region_copy);
ops.push_back (Operations::fixed_time_region_copy);
ops.sort ();
/* See if any of the current operations match the ones that we want */
list<GQuark> in;
set_intersection (_current_trans_quarks.begin(), _current_trans_quarks.end(), ops.begin(), ops.end(), back_inserter (in));
/* If so, update the session range markers */
if (!in.empty ()) {
maybe_update_session_range (r->position (), r->end ());
}
}
/** Update the session range markers if a is before the current start or
* b is after the current end.
*/
void
Session::maybe_update_session_range (timepos_t const & a, timepos_t const & b)
{
if (loading ()) {
return;
}
samplepos_t session_end_marker_shift_samples = session_end_shift * _nominal_sample_rate;
if (_session_range_location == 0) {
set_session_extents (a, b + timepos_t (session_end_marker_shift_samples));
} else {
if (_session_range_is_free && (a < _session_range_location->start())) {
_session_range_location->set_start (a);
}
if (_session_range_is_free && (b > _session_range_location->end())) {
_session_range_location->set_end (b);
}
}
}
void
Session::set_session_range_is_free (bool yn)
{
_session_range_is_free = yn;
}
void
Session::playlist_ranges_moved (list<Temporal::RangeMove> const & ranges)
{
for (list<Temporal::RangeMove>::const_iterator i = ranges.begin(); i != ranges.end(); ++i) {
maybe_update_session_range (i->from, i->to);
}
}
void
Session::playlist_regions_extended (list<Temporal::Range> const & ranges)
{
for (list<Temporal::Range>::const_iterator i = ranges.begin(); i != ranges.end(); ++i) {
maybe_update_session_range (i->start(), i->end());
}
}
/* Region management */
boost::shared_ptr<Region>
Session::find_whole_file_parent (boost::shared_ptr<Region const> child) const
{
const RegionFactory::RegionMap& regions (RegionFactory::regions());
RegionFactory::RegionMap::const_iterator i;
boost::shared_ptr<Region> region;
Glib::Threads::Mutex::Lock lm (region_lock);
for (i = regions.begin(); i != regions.end(); ++i) {
region = i->second;
if (region->whole_file()) {
if (child->source_equivalent (region)) {
return region;
}
}
}
return boost::shared_ptr<Region> ();
}
int
Session::destroy_sources (list<boost::shared_ptr<Source> > const& srcs)
{
set<boost::shared_ptr<Region> > relevant_regions;
for (list<boost::shared_ptr<Source> >::const_iterator s = srcs.begin(); s != srcs.end(); ++s) {
RegionFactory::get_regions_using_source (*s, relevant_regions);
}
for (set<boost::shared_ptr<Region> >::iterator r = relevant_regions.begin(); r != relevant_regions.end(); ) {
set<boost::shared_ptr<Region> >::iterator tmp;
tmp = r;
++tmp;
_playlists->destroy_region (*r);
RegionFactory::map_remove (*r);
(*r)->drop_sources ();
(*r)->drop_references ();
relevant_regions.erase (r);
r = tmp;
}
for (list<boost::shared_ptr<Source> >::const_iterator s = srcs.begin(); s != srcs.end(); ++s) {
{
Glib::Threads::Mutex::Lock ls (source_lock);
/* remove from the main source list */
sources.erase ((*s)->id());
}
(*s)->mark_for_remove ();
(*s)->drop_references ();
SourceRemoved (boost::weak_ptr<Source> (*s)); /* EMIT SIGNAL */
}
return 0;
}
int
Session::remove_last_capture ()
{
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 ();
}
}
destroy_sources (srcs);
/* save state so we don't end up with a session file
* referring to non-existent sources.
*/
save_state ();
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
Session::add_source (boost::shared_ptr<Source> source)
{
pair<SourceMap::key_type, SourceMap::mapped_type> entry;
pair<SourceMap::iterator,bool> result;
entry.first = source->id();
entry.second = source;
{
Glib::Threads::Mutex::Lock lm (source_lock);
result = sources.insert (entry);
}
if (result.second) {
/* yay, new source */
boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (source);
if (fs) {
if (!fs->within_session()) {
ensure_search_path_includes (Glib::path_get_dirname (fs->path()), fs->type());
}
}
set_dirty();
boost::shared_ptr<AudioFileSource> afs;
if ((afs = boost::dynamic_pointer_cast<AudioFileSource>(source)) != 0) {
if (Config->get_auto_analyse_audio()) {
Analyser::queue_source_for_analysis (source, false);
}
}
source->DropReferences.connect_same_thread (*this, boost::bind (&Session::remove_source, this, boost::weak_ptr<Source> (source)));
SourceAdded (boost::weak_ptr<Source> (source)); /* EMIT SIGNAL */
}
}
void
Session::remove_source (boost::weak_ptr<Source> src)
{
if (deletion_in_progress ()) {
return;
}
SourceMap::iterator i;
boost::shared_ptr<Source> source = src.lock();
if (!source) {
return;
}
{
Glib::Threads::Mutex::Lock lm (source_lock);
if ((i = sources.find (source->id())) != sources.end()) {
sources.erase (i);
SourceRemoved (src); /* EMIT SIGNAL */
} else {
return;
}
}
if (source->empty ()) {
/* No need to save when empty sources are removed.
* This is likely due to disk-writer initial dummies
* where files don't even exist on disk.
*/
return;
}
if (!in_cleanup () && !loading ()) {
/* save state so we don't end up with a session file
* referring to non-existent sources.
*/
save_state ();
}
}
boost::shared_ptr<Source>
Session::source_by_id (const PBD::ID& id)
{
Glib::Threads::Mutex::Lock lm (source_lock);
SourceMap::iterator i;
boost::shared_ptr<Source> source;
if ((i = sources.find (id)) != sources.end()) {
source = i->second;
}
return source;
}
boost::shared_ptr<AudioFileSource>
Session::audio_source_by_path_and_channel (const string& path, uint16_t chn) const
{
/* Restricted to audio files because only audio sources have channel
as a property.
*/
Glib::Threads::Mutex::Lock lm (source_lock);
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
boost::shared_ptr<AudioFileSource> afs
= boost::dynamic_pointer_cast<AudioFileSource>(i->second);
if (afs && afs->path() == path && chn == afs->channel()) {
return afs;
}
}
return boost::shared_ptr<AudioFileSource>();
}
boost::shared_ptr<MidiSource>
Session::midi_source_by_path (const std::string& path, bool need_source_lock) const
{
/* Restricted to MIDI files because audio sources require a channel
for unique identification, in addition to a path.
*/
Glib::Threads::Mutex::Lock lm (source_lock, Glib::Threads::NOT_LOCK);
if (need_source_lock) {
lm.acquire ();
}
for (SourceMap::const_iterator s = sources.begin(); s != sources.end(); ++s) {
boost::shared_ptr<MidiSource> ms
= boost::dynamic_pointer_cast<MidiSource>(s->second);
boost::shared_ptr<FileSource> fs
= boost::dynamic_pointer_cast<FileSource>(s->second);
if (ms && fs && fs->path() == path) {
return ms;
}
}
return boost::shared_ptr<MidiSource>();
}
uint32_t
Session::count_sources_by_origin (const string& path)
{
uint32_t cnt = 0;
Glib::Threads::Mutex::Lock lm (source_lock);
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
boost::shared_ptr<FileSource> fs
= boost::dynamic_pointer_cast<FileSource>(i->second);
if (fs && fs->origin() == path) {
++cnt;
}
}
return cnt;
}
static string
peak_file_helper (const string& peak_path, const string& file_path, const string& file_base, bool hash) {
if (hash) {
std::string checksum = Glib::Checksum::compute_checksum(Glib::Checksum::CHECKSUM_SHA1, file_path + G_DIR_SEPARATOR + file_base);
return Glib::build_filename (peak_path, checksum + peakfile_suffix);
} else {
return Glib::build_filename (peak_path, file_base + peakfile_suffix);
}
}
string
Session::construct_peak_filepath (const string& filepath, const bool in_session, const bool old_peak_name) const
{
string interchange_dir_string = string (interchange_dir_name) + G_DIR_SEPARATOR;
if (Glib::path_is_absolute (filepath)) {
/* rip the session dir from the audiofile source */
string session_path;
bool in_another_session = true;
if (filepath.find (interchange_dir_string) != string::npos) {
session_path = Glib::path_get_dirname (filepath); /* now ends in audiofiles */
session_path = Glib::path_get_dirname (session_path); /* now ends in session name */
session_path = Glib::path_get_dirname (session_path); /* now ends in interchange */
session_path = Glib::path_get_dirname (session_path); /* now has session path */
/* see if it is within our session */
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
if (i->path == session_path) {
in_another_session = false;
break;
}
}
} else {
in_another_session = false;
}
if (in_another_session) {
SessionDirectory sd (session_path);
return peak_file_helper (sd.peak_path(), "", Glib::path_get_basename (filepath), !old_peak_name);
}
}
/* 1) if file belongs to this session
* it may be a relative path (interchange/...)
* or just basename (session_state, remove source)
* -> just use the basename
*/
std::string filename = Glib::path_get_basename (filepath);
std::string path;
/* 2) if the file is outside our session dir:
* (imported but not copied) add the path for check-summming */
if (!in_session) {
path = Glib::path_get_dirname (filepath);
}
return peak_file_helper (_session_dir->peak_path(), path, Glib::path_get_basename (filepath), !old_peak_name);
}
string
Session::new_audio_source_path_for_embedded (const std::string& path)
{
/* embedded source:
*
* we know that the filename is already unique because it exists
* out in the filesystem.
*
* However, when we bring it into the session, we could get a
* collision.
*
* Eg. two embedded files:
*
* /foo/bar/baz.wav
* /frob/nic/baz.wav
*
* When merged into session, these collide.
*
* There will not be a conflict with in-memory sources
* because when the source was created we already picked
* a unique name for it.
*
* This collision is not likely to be common, but we have to guard
* against it. So, if there is a collision, take the md5 hash of the
* the path, and use that as the filename instead.
*/
SessionDirectory sdir (get_best_session_directory_for_new_audio());
string base = Glib::path_get_basename (path);
string newpath = Glib::build_filename (sdir.sound_path(), base);
if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
MD5 md5;
md5.digestString (path.c_str());
md5.writeToString ();
base = md5.digestChars;
string ext = get_suffix (path);
if (!ext.empty()) {
base += '.';
base += ext;
}
newpath = Glib::build_filename (sdir.sound_path(), base);
/* if this collides, we're screwed */
if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
error << string_compose (_("Merging embedded file %1: name collision AND md5 hash collision!"), path) << endmsg;
return string();
}
}
return newpath;
}
/** Return true if there are no audio file sources that use @p name as
* the filename component of their path.
*
* Return false otherwise.
*
* This method MUST ONLY be used to check in-session, mono files since it
* hard-codes the channel of the audio file source we are looking for as zero.
*
* If/when Ardour supports native files in non-mono formats, the logic here
* will need to be revisited.
*/
bool
Session::audio_source_name_is_unique (const string& name)
{
std::vector<string> sdirs = source_search_path (DataType::AUDIO);
uint32_t existing = 0;
for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
/* note that we search *without* the extension so that
we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf"
in the event that this new name is required for
a file format change.
*/
const string spath = *i;
if (matching_unsuffixed_filename_exists_in (spath, name)) {
existing++;
break;
}
/* it is possible that we have the path already
* assigned to a source that has not yet been written
* (ie. the write source for a diskstream). we have to
* check this in order to make sure that our candidate
* path isn't used again, because that can lead to
* two Sources point to the same file with different
* notions of their removability.
*/
string possible_path = Glib::build_filename (spath, name);
if (audio_source_by_path_and_channel (possible_path, 0)) {
existing++;
break;
}
}
return (existing == 0);
}
string
Session::format_audio_source_name (const string& legalized_base, uint32_t nchan, uint32_t chan, bool take_required, uint32_t cnt, bool related_exists)
{
ostringstream sstr;
const string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
sstr << legalized_base;
if (take_required || related_exists) {
sstr << '-';
sstr << cnt;
}
if (nchan == 2) {
if (chan == 0) {
sstr << "%L";
} else {
sstr << "%R";
}
} else if (nchan > 2) {
if (nchan < 26) {
sstr << '%';
sstr << 'a' + chan;
} else {
/* XXX what? more than 26 channels! */
sstr << '%';
sstr << chan+1;
}
}
sstr << ext;
return sstr.str();
}
/** Return a unique name based on \a base for a new internal audio source */
string
Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool take_required)
{
uint32_t cnt;
string possible_name;
const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name
string legalized;
bool some_related_source_name_exists = false;
legalized = legalize_for_path (base);
// Find a "version" of the base name that doesn't exist in any of the possible directories.
for (cnt = 1; cnt <= limit; ++cnt) {
possible_name = format_audio_source_name (legalized, nchan, chan, take_required, cnt, some_related_source_name_exists);
if (audio_source_name_is_unique (possible_name)) {
break;
}
some_related_source_name_exists = true;
if (cnt > limit) {
error << string_compose(
_("There are already %1 recordings for %2, which I consider too many."),
limit, base) << endmsg;
destroy ();
throw failed_constructor();
}
}
/* We've established that the new name does not exist in any session
* directory, so now find out which one we should use for this new
* audio source.
*/
SessionDirectory sdir (get_best_session_directory_for_new_audio());
std::string s = Glib::build_filename (sdir.sound_path(), possible_name);
return s;
}
/** Return a unique name based on `base` for a new internal MIDI source */
string
Session::new_midi_source_path (const string& base, bool need_lock)
{
string possible_path;
string possible_name;
possible_name = legalize_for_path (base);
// Find a "version" of the file name that doesn't exist in any of the possible directories.
std::vector<string> sdirs = source_search_path(DataType::MIDI);
/* - the main session folder is the first in the vector.
* - after checking all locations for file-name uniqueness,
* we keep the one from the last iteration as new file name
* - midi files are small and should just be kept in the main session-folder
*
* -> reverse the array, check main session folder last and use that as location
* for MIDI files.
*/
std::reverse(sdirs.begin(), sdirs.end());
while (true) {
possible_name = bump_name_once (possible_name, '-');
uint32_t existing = 0;
for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
possible_path = Glib::build_filename (*i, possible_name + ".mid");
if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) {
existing++;
}
if (midi_source_by_path (possible_path, need_lock)) {
existing++;
}
}
if (possible_path.size () >= PATH_MAX) {
error << string_compose(
_("There are already many recordings for %1, resulting in a too long file-path %2."),
base, possible_path) << endmsg;
destroy ();
return 0;
}
if (existing == 0) {
break;
}
}
/* No need to "find best location" for software/app-based RAID, because
MIDI is so small that we always put it in the same place.
*/
return possible_path;
}
/** Create a new within-session audio source */
boost::shared_ptr<AudioFileSource>
Session::create_audio_source_for_session (size_t n_chans, string const & base, uint32_t chan)
{
const string path = new_audio_source_path (base, n_chans, chan, true);
if (!path.empty()) {
return boost::dynamic_pointer_cast<AudioFileSource> (SourceFactory::createWritable (DataType::AUDIO, *this, path, sample_rate(), true, true));
} else {
throw failed_constructor ();
}
}
/** Create a new within-session MIDI source */
boost::shared_ptr<MidiSource>
Session::create_midi_source_for_session (string const & basic_name)
{
const string path = new_midi_source_path (basic_name);
if (!path.empty()) {
return boost::dynamic_pointer_cast<SMFSource> (SourceFactory::createWritable (DataType::MIDI, *this, path, sample_rate()));
} else {
throw failed_constructor ();
}
}
/** Create a new within-session MIDI source */
boost::shared_ptr<MidiSource>
Session::create_midi_source_by_stealing_name (boost::shared_ptr<Track> track)
{
/* the caller passes in the track the source will be used in,
so that we can keep the numbering sane.
Rationale: a track with the name "Foo" that has had N
captures carried out so far will ALREADY have a write source
named "Foo-N+1.mid" waiting to be used for the next capture.
If we call new_midi_source_name() we will get "Foo-N+2". But
there is no region corresponding to "Foo-N+1", so when
"Foo-N+2" appears in the track, the gap presents the user
with odd behaviour - why did it skip past Foo-N+1?
We could explain this to the user in some odd way, but
instead we rename "Foo-N+1.mid" as "Foo-N+2.mid", and then
use "Foo-N+1" here.
If that attempted rename fails, we get "Foo-N+2.mid" anyway.
*/
boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (track);
assert (mt);
std::string name = track->steal_write_source_name ();
if (name.empty()) {
return boost::shared_ptr<MidiSource>();
}
/* MIDI files are small, just put them in the first location of the
session source search path.
*/
const string path = Glib::build_filename (source_search_path (DataType::MIDI).front(), name);
return boost::dynamic_pointer_cast<SMFSource> (SourceFactory::createWritable (DataType::MIDI, *this, path, 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)
{
if (playlist->hidden()) {
return;
}
_playlists->add (playlist);
if (unused) {
playlist->release();
}
set_dirty();
}
void
Session::remove_playlist (boost::weak_ptr<Playlist> weak_playlist)
{
if (deletion_in_progress ()) {
return;
}
boost::shared_ptr<Playlist> playlist (weak_playlist.lock());
if (!playlist) {
return;
}
_playlists->remove (playlist);
set_dirty();
}
void
Session::set_audition (boost::shared_ptr<Region> r)
{
pending_audition_region = r;
add_post_transport_work (PostTransportAudition);
_butler->schedule_transport_work ();
}
void
Session::audition_playlist ()
{
SessionEvent* ev = new SessionEvent (SessionEvent::Audition, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0);
ev->region.reset ();
queue_event (ev);
}
void
Session::load_io_plugin (boost::shared_ptr<IOPlug> ioplugin)
{
{
RCUWriter<IOPlugList> writer (_io_plugins);
boost::shared_ptr<IOPlugList> iop = writer.get_copy ();
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
ioplugin->ensure_io ();
iop->push_back (ioplugin);
ioplugin->LatencyChanged.connect_same_thread (*this, boost::bind (&Session::update_latency_compensation, this, true, false));
}
IOPluginsChanged (); /* EMIT SIGNAL */
set_dirty();
}
bool
Session::unload_io_plugin (boost::shared_ptr<IOPlug> ioplugin)
{
{
RCUWriter<IOPlugList> writer (_io_plugins);
boost::shared_ptr<IOPlugList> iop = writer.get_copy ();
auto i = find (iop->begin (), iop->end (), ioplugin);
if (i == iop->end ()) {
return false;
}
(*i)->drop_references ();
iop->erase (i);
}
IOPluginsChanged (); /* EMIT SIGNAL */
set_dirty();
return true;
}
void
Session::register_lua_function (
const std::string& name,
const std::string& script,
const LuaScriptParamList& args
)
{
Glib::Threads::Mutex::Lock lm (lua_lock);
lua_State* L = lua.getState();
const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
luabridge::LuaRef tbl_arg (luabridge::newTable(L));
for (LuaScriptParamList::const_iterator i = args.begin(); i != args.end(); ++i) {
if ((*i)->optional && !(*i)->is_set) { continue; }
tbl_arg[(*i)->name] = (*i)->value;
}
(*_lua_add)(name, bytecode, tbl_arg); // throws luabridge::LuaException
lm.release();
LuaScriptsChanged (); /* EMIT SIGNAL */
set_dirty();
}
void
Session::unregister_lua_function (const std::string& name)
{
Glib::Threads::Mutex::Lock lm (lua_lock);
(*_lua_del)(name); // throws luabridge::LuaException
lua.collect_garbage ();
lm.release();
LuaScriptsChanged (); /* EMIT SIGNAL */
set_dirty();
}
std::vector<std::string>
Session::registered_lua_functions ()
{
Glib::Threads::Mutex::Lock lm (lua_lock);
std::vector<std::string> rv;
try {
luabridge::LuaRef list ((*_lua_list)());
for (luabridge::Iterator i (list); !i.isNil (); ++i) {
if (!i.key ().isString ()) { assert(0); continue; }
rv.push_back (i.key ().cast<std::string> ());
}
} catch (...) { }
return rv;
}
static void _lua_print (std::string s) {
#ifndef NDEBUG
std::cout << "LuaSession: " << s << "\n";
#endif
PBD::info << "LuaSession: " << s << endmsg;
}
void
Session::try_run_lua (pframes_t nframes)
{
if (_n_lua_scripts == 0) return;
Glib::Threads::Mutex::Lock tm (lua_lock, Glib::Threads::TRY_LOCK);
if (tm.locked ()) {
try { (*_lua_run)(nframes); } catch (...) { }
lua.collect_garbage_step ();
}
}
void
Session::setup_lua ()
{
lua.Print.connect (&_lua_print);
lua.sandbox (true);
lua.do_command (
"function ArdourSession ()"
" local self = { scripts = {}, instances = {} }"
""
" local remove = function (n)"
" self.scripts[n] = nil"
" self.instances[n] = nil"
" Session:scripts_changed()" // call back
" end"
""
" local addinternal = function (n, f, a)"
" assert(type(n) == 'string', 'function-name must be string')"
" assert(type(f) == 'function', 'Given script is a not a function')"
" assert(type(a) == 'table' or type(a) == 'nil', 'Given argument is invalid')"
" assert(self.scripts[n] == nil, 'Callback \"'.. n ..'\" already exists.')"
" self.scripts[n] = { ['f'] = f, ['a'] = a }"
" local env = { print = print, tostring = tostring, assert = assert, ipairs = ipairs, error = error, select = select, string = string, type = type, tonumber = tonumber, collectgarbage = collectgarbage, pairs = pairs, math = math, table = table, pcall = pcall, bit32=bit32, Session = Session, PBD = PBD, Temporal = Temporal, Timecode = Timecode, Evoral = Evoral, C = C, ARDOUR = ARDOUR }"
" self.instances[n] = load (string.dump(f, true), nil, nil, env)(a)"
" Session:scripts_changed()" // call back
" end"
""
" local add = function (n, b, a)"
" assert(type(b) == 'string', 'ByteCode must be string')"
" load (b)()" // assigns f
" assert(type(f) == 'string', 'Assigned ByteCode must be string')"
" addinternal (n, load(f), a)"
" end"
""
" local run = function (...)"
" for n, s in pairs (self.instances) do"
" local status, err = pcall (s, ...)"
" if not status then"
" print ('fn \"'.. n .. '\": ', err)"
" remove (n)"
" end"
" end"
" collectgarbage(\"step\")"
" end"
""
" local cleanup = function ()"
" self.scripts = nil"
" self.instances = nil"
" end"
""
" local list = function ()"
" local rv = {}"
" for n, _ in pairs (self.scripts) do"
" rv[n] = true"
" end"
" return rv"
" end"
""
" local function basic_serialize (o)"
" if type(o) == \"number\" then"
" return tostring(o)"
" else"
" return string.format(\"%q\", o)"
" end"
" end"
""
" local function serialize (name, value)"
" local rv = name .. ' = '"
" collectgarbage()"
" if type(value) == \"number\" or type(value) == \"string\" or type(value) == \"nil\" then"
" return rv .. basic_serialize(value) .. ' '"
" elseif type(value) == \"table\" then"
" rv = rv .. '{} '"
" for k,v in pairs(value) do"
" local fieldname = string.format(\"%s[%s]\", name, basic_serialize(k))"
" rv = rv .. serialize(fieldname, v) .. ' '"
" collectgarbage()" // string concatenation allocates a new string :(
" end"
" return rv;"
" elseif type(value) == \"function\" then"
" return rv .. string.format(\"%q\", string.dump(value, true))"
" else"
" error('cannot save a ' .. type(value))"
" end"
" end"
""
""
" local save = function ()"
" return (serialize('scripts', self.scripts))"
" end"
""
" local restore = function (state)"
" self.scripts = {}"
" load (state)()"
" for n, s in pairs (scripts) do"
" addinternal (n, load(s['f']), s['a'])"
" end"
" end"
""
" return { run = run, add = add, remove = remove,"
" list = list, restore = restore, save = save, cleanup = cleanup}"
" end"
" "
" sess = ArdourSession ()"
" ArdourSession = nil"
" "
"function ardour () end"
);
lua_State* L = lua.getState();
try {
luabridge::LuaRef lua_sess = luabridge::getGlobal (L, "sess");
lua.do_command ("sess = nil"); // hide it.
lua.do_command ("collectgarbage()");
_lua_run = new luabridge::LuaRef(lua_sess["run"]);
_lua_add = new luabridge::LuaRef(lua_sess["add"]);
_lua_del = new luabridge::LuaRef(lua_sess["remove"]);
_lua_list = new luabridge::LuaRef(lua_sess["list"]);
_lua_save = new luabridge::LuaRef(lua_sess["save"]);
_lua_load = new luabridge::LuaRef(lua_sess["restore"]);
_lua_cleanup = new luabridge::LuaRef(lua_sess["cleanup"]);
} catch (luabridge::LuaException const& e) {
fatal << string_compose (_("programming error: %1"),
std::string ("Failed to setup session Lua interpreter") + e.what ())
<< endmsg;
abort(); /*NOTREACHED*/
} catch (...) {
fatal << string_compose (_("programming error: %1"),
X_("Failed to setup session Lua interpreter"))
<< endmsg;
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");
}
void
Session::scripts_changed ()
{
assert (!lua_lock.trylock()); // must hold lua_lock
try {
luabridge::LuaRef list ((*_lua_list)());
int cnt = 0;
for (luabridge::Iterator i (list); !i.isNil (); ++i) {
if (!i.key ().isString ()) { assert(0); continue; }
++cnt;
}
_n_lua_scripts = cnt;
} catch (luabridge::LuaException const& e) {
fatal << string_compose (_("programming error: %1"),
std::string ("Indexing Lua Session Scripts failed.") + e.what ())
<< endmsg;
abort(); /*NOTREACHED*/
} catch (...) {
fatal << string_compose (_("programming error: %1"),
X_("Indexing Lua Session Scripts failed."))
<< endmsg;
abort(); /*NOTREACHED*/
}
}
void
Session::non_realtime_set_audition ()
{
assert (pending_audition_region);
auditioner->audition_region (pending_audition_region);
pending_audition_region.reset ();
AuditionActive (true); /* EMIT SIGNAL */
}
void
Session::audition_region (boost::shared_ptr<Region> r)
{
SessionEvent* ev = new SessionEvent (SessionEvent::Audition, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0);
ev->region = r;
queue_event (ev);
}
void
Session::cancel_audition ()
{
if (!auditioner) {
return;
}
if (auditioner->auditioning()) {
auditioner->cancel_audition ();
AuditionActive (false); /* EMIT SIGNAL */
}
}
bool
Session::is_auditioning () const
{
/* can be called before we have an auditioner object */
if (auditioner) {
return auditioner->auditioning();
} else {
return false;
}
}
void
Session::graph_reordered (bool called_from_backend)
{
/* don't do this stuff if we are setting up connections
from a set_state() call or creating new tracks. Ditto for deletion.
*/
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.
*
* XXX: Is this required? When the graph-order callback
* is initiated by the backend, it is always followed by
* a latency callback.
*/
update_latency_compensation (true, called_from_backend);
}
/** @return Number of samples that there is disk space available to write,
* if known.
*/
boost::optional<samplecnt_t>
Session::available_capture_duration ()
{
Glib::Threads::Mutex::Lock lm (space_lock);
if (_total_free_4k_blocks_uncertain) {
return boost::optional<samplecnt_t> ();
}
float sample_bytes_on_disk = 4.0; // keep gcc happy
switch (config.get_native_file_data_format()) {
case FormatFloat:
sample_bytes_on_disk = 4.0;
break;
case FormatInt24:
sample_bytes_on_disk = 3.0;
break;
case FormatInt16:
sample_bytes_on_disk = 2.0;
break;
default:
/* impossible, but keep some gcc versions happy */
fatal << string_compose (_("programming error: %1"),
X_("illegal native file data format"))
<< endmsg;
abort(); /*NOTREACHED*/
}
double scale = 4096.0 / sample_bytes_on_disk;
if (_total_free_4k_blocks * scale > (double) max_samplecnt) {
return max_samplecnt;
}
return (samplecnt_t) floor (_total_free_4k_blocks * scale);
}
void
Session::tempo_map_changed ()
{
clear_clicks ();
sync_cues ();
foreach_route (&Route::tempo_map_changed);
_playlists->update_after_tempo_map_change ();
set_dirty ();
}
/** Ensures that all buffers (scratch, send, silent, etc) are allocated for
* the given count with the current block size.
*/
void
Session::ensure_buffers (ChanCount howmany)
{
BufferManager::ensure_buffers (howmany, bounce_processing() ? bounce_chunk_size : 0);
}
void
Session::ensure_buffer_set(BufferSet& buffers, const ChanCount& count)
{
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
buffers.ensure_buffers(*t, count.get(*t), _engine.raw_buffer_size(*t));
}
}
uint32_t
Session::next_insert_id ()
{
/* this doesn't really loop forever. just think about it */
while (true) {
for (boost::dynamic_bitset<uint32_t>::size_type n = 1; n < insert_bitset.size(); ++n) {
if (!insert_bitset[n]) {
insert_bitset[n] = true;
return n;
}
}
/* none available, so resize and try again */
insert_bitset.resize (insert_bitset.size() + 16, false);
}
}
uint32_t
Session::next_send_id ()
{
/* this doesn't really loop forever. just think about it */
while (true) {
for (boost::dynamic_bitset<uint32_t>::size_type n = 1; n < send_bitset.size(); ++n) {
if (!send_bitset[n]) {
send_bitset[n] = true;
return n;
}
}
/* none available, so resize and try again */
send_bitset.resize (send_bitset.size() + 16, false);
}
}
uint32_t
Session::next_aux_send_id ()
{
/* this doesn't really loop forever. just think about it */
while (true) {
for (boost::dynamic_bitset<uint32_t>::size_type n = 1; n < aux_send_bitset.size(); ++n) {
if (!aux_send_bitset[n]) {
aux_send_bitset[n] = true;
return n;
}
}
/* none available, so resize and try again */
aux_send_bitset.resize (aux_send_bitset.size() + 16, false);
}
}
uint32_t
Session::next_return_id ()
{
/* this doesn't really loop forever. just think about it */
while (true) {
for (boost::dynamic_bitset<uint32_t>::size_type n = 1; n < return_bitset.size(); ++n) {
if (!return_bitset[n]) {
return_bitset[n] = true;
return n;
}
}
/* none available, so resize and try again */
return_bitset.resize (return_bitset.size() + 16, false);
}
}
void
Session::mark_send_id (uint32_t id)
{
if (id >= send_bitset.size()) {
send_bitset.resize (id+16, false);
}
if (send_bitset[id]) {
warning << string_compose (_("send ID %1 appears to be in use already"), id) << endmsg;
}
send_bitset[id] = true;
}
void
Session::mark_aux_send_id (uint32_t id)
{
if (id >= aux_send_bitset.size()) {
aux_send_bitset.resize (id+16, false);
}
if (aux_send_bitset[id]) {
warning << string_compose (_("aux send ID %1 appears to be in use already"), id) << endmsg;
}
aux_send_bitset[id] = true;
}
void
Session::mark_return_id (uint32_t id)
{
if (id >= return_bitset.size()) {
return_bitset.resize (id+16, false);
}
if (return_bitset[id]) {
warning << string_compose (_("return ID %1 appears to be in use already"), id) << endmsg;
}
return_bitset[id] = true;
}
void
Session::mark_insert_id (uint32_t id)
{
if (id >= insert_bitset.size()) {
insert_bitset.resize (id+16, false);
}
if (insert_bitset[id]) {
warning << string_compose (_("insert ID %1 appears to be in use already"), id) << endmsg;
}
insert_bitset[id] = true;
}
void
Session::unmark_send_id (uint32_t id)
{
if (deletion_in_progress ()) {
return;
}
if (id < send_bitset.size()) {
send_bitset[id] = false;
}
}
void
Session::unmark_aux_send_id (uint32_t id)
{
if (deletion_in_progress ()) {
return;
}
if (id < aux_send_bitset.size()) {
aux_send_bitset[id] = false;
}
}
void
Session::unmark_return_id (uint32_t id)
{
if (deletion_in_progress ()) {
return;
}
if (id < return_bitset.size()) {
return_bitset[id] = false;
}
}
void
Session::unmark_insert_id (uint32_t id)
{
if (deletion_in_progress ()) {
return;
}
if (id < insert_bitset.size()) {
insert_bitset[id] = false;
}
}
void
Session::reset_native_file_format ()
{
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) {
/* 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);
}
}
}
bool
Session::route_name_unique (string n) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::const_iterator i = r->begin(); i != r->end(); ++i) {
if ((*i)->name() == n) {
return false;
}
}
return true;
}
bool
Session::route_name_internal (string n) const
{
if (auditioner && auditioner->name() == n) {
return true;
}
if (_click_io && _click_io->name() == n) {
return true;
}
return false;
}
int
Session::freeze_all (InterThreadInfo& itt)
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
boost::shared_ptr<Track> t;
if ((t = boost::dynamic_pointer_cast<Track>(*i)) != 0) {
/* XXX this is wrong because itt.progress will keep returning to zero at the start
of every track.
*/
t->freeze_me (itt);
}
}
return 0;
}
struct MidiSourceLockMap
{
boost::shared_ptr<MidiSource> src;
Source::WriterLock lock;
MidiSourceLockMap (boost::shared_ptr<MidiSource> midi_source) : src (midi_source), lock (src->mutex()) {}
};
boost::shared_ptr<Region>
Session::write_one_track (Track& track, samplepos_t start, samplepos_t end,
bool /*overwrite*/, vector<boost::shared_ptr<Source> >& srcs,
InterThreadInfo& itt,
boost::shared_ptr<Processor> endpoint, bool include_endpoint,
bool for_export, bool for_freeze, std::string const& name)
{
boost::shared_ptr<Region> result;
boost::shared_ptr<Playlist> playlist;
boost::shared_ptr<Source> source;
ChanCount diskstream_channels (track.n_channels());
samplepos_t position;
samplecnt_t this_chunk;
samplepos_t to_do;
samplepos_t latency_skip;
samplepos_t out_pos;
BufferSet buffers;
samplepos_t len = end - start;
bool need_block_size_reset = false;
ChanCount const max_proc = track.max_processor_streams ();
string legal_playlist_name;
string possible_path;
MidiBuffer resolved (256);
MidiNoteTracker tracker;
DataType data_type = track.data_type();
std::vector<MidiSourceLockMap*> midi_source_locks;
if (end <= start) {
error << string_compose (_("Cannot write a range where end <= start (e.g. %1 <= %2)"),
end, start) << endmsg;
return result;
}
diskstream_channels = track.bounce_get_output_streams (diskstream_channels, endpoint,
include_endpoint, for_export, for_freeze);
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;
}
/* block all process callback handling, so that thread-buffers
* are available here.
*/
block_processing ();
_bounce_processing_active = true;
/* call tree *MUST* hold route_lock */
if ((playlist = track.playlist()) == 0) {
goto out;
}
if (name.length() > 0) {
/*if the user passed in a name, we will use it, and also prepend the resulting sources with that name*/
legal_playlist_name.append (legalize_for_path (name));
} else {
legal_playlist_name.append (legalize_for_path (playlist->name ()));
}
for (uint32_t chan_n = 0; chan_n < diskstream_channels.n(data_type); ++chan_n) {
string path = ((data_type == DataType::AUDIO)
? new_audio_source_path (legal_playlist_name, diskstream_channels.n_audio(), chan_n, false)
: new_midi_source_path (legal_playlist_name));
if (path.empty()) {
goto out;
}
try {
source = SourceFactory::createWritable (data_type, *this, path, sample_rate());
}
catch (failed_constructor& err) {
error << string_compose (_("cannot create new file \"%1\" for %2"), path, track.name()) << endmsg;
goto out;
}
source->set_captured_for(track.name());
time_t now;
time (&now);
Glib::DateTime tm (Glib::DateTime::create_now_local (now));
source->set_take_id (tm.format ("%F %H.%M.%S"));
srcs.push_back (source);
}
/* tell redirects that care that we are about to use a much larger
* blocksize. this will flush all plugins too, so that they are ready
* to be used for this process.
*/
need_block_size_reset = true;
track.set_block_size (bounce_chunk_size);
_engine.main_thread()->get_buffers ();
position = start;
to_do = len;
latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export, for_freeze);
/* create a set of reasonably-sized buffers */
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
buffers.ensure_buffers(*t, max_proc.get(*t), bounce_chunk_size);
}
buffers.set_count (max_proc);
/* prepare MIDI files */
for (vector<boost::shared_ptr<Source> >::iterator src = srcs.begin(); src != srcs.end(); ++src) {
boost::shared_ptr<MidiSource> ms = boost::dynamic_pointer_cast<MidiSource>(*src);
if (ms) {
midi_source_locks.push_back (new MidiSourceLockMap (ms));
ms->mark_streaming_write_started (midi_source_locks.back()->lock);
}
}
/* prepare audio files */
for (vector<boost::shared_ptr<Source> >::iterator src = srcs.begin(); src != srcs.end(); ++src) {
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
if (afs) {
afs->prepare_for_peakfile_writes ();
}
}
/* process */
out_pos = start;
while (to_do && !itt.cancel) {
this_chunk = min (to_do, bounce_chunk_size);
if (track.export_stuff (buffers, start, this_chunk, endpoint, include_endpoint, for_export, for_freeze, tracker)) {
goto out;
}
start += this_chunk;
to_do -= this_chunk;
itt.progress = (float) (1.0 - ((double) to_do / len));
if (latency_skip >= bounce_chunk_size) {
latency_skip -= bounce_chunk_size;
continue;
}
const samplecnt_t current_chunk = this_chunk - latency_skip;
uint32_t n = 0;
for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) {
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
boost::shared_ptr<MidiSource> ms;
if (afs) {
if (afs->write (buffers.get_audio(n).data(latency_skip), current_chunk) != current_chunk) {
goto out;
}
}
}
/* XXX NUTEMPO fix this to not use samples */
for (vector<MidiSourceLockMap*>::iterator m = midi_source_locks.begin(); m != midi_source_locks.end(); ++m) {
const MidiBuffer& buf = buffers.get_midi(0);
for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
Evoral::Event<samplepos_t> ev = *i;
if (!endpoint || for_export) {
ev.set_time(ev.time() - position);
} else {
/* MidiTrack::export_stuff moves event to the current cycle */
ev.set_time(ev.time() + out_pos - position);
}
(*m)->src->append_event_samples ((*m)->lock, ev, (*m)->src->natural_position().samples());
}
}
out_pos += current_chunk;
latency_skip = 0;
}
tracker.resolve_notes (resolved, end-1);
if (!resolved.empty()) {
for (vector<MidiSourceLockMap*>::iterator m = midi_source_locks.begin(); m != midi_source_locks.end(); ++m) {
for (MidiBuffer::iterator i = resolved.begin(); i != resolved.end(); ++i) {
Evoral::Event<samplepos_t> ev = *i;
if (!endpoint || for_export) {
ev.set_time(ev.time() - position);
}
(*m)->src->append_event_samples ((*m)->lock, ev, (*m)->src->natural_position().samples());
}
}
}
for (vector<MidiSourceLockMap*>::iterator m = midi_source_locks.begin(); m != midi_source_locks.end(); ++m) {
delete *m;
}
midi_source_locks.clear ();
/* post-roll, pick up delayed processor output */
latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export, for_freeze);
while (latency_skip && !itt.cancel) {
this_chunk = min (latency_skip, bounce_chunk_size);
latency_skip -= this_chunk;
buffers.silence (this_chunk, 0);
track.bounce_process (buffers, start, this_chunk, endpoint, include_endpoint, for_export, for_freeze);
start += this_chunk;
uint32_t n = 0;
for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) {
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
if (afs) {
if (afs->write (buffers.get_audio(n).data(), this_chunk) != this_chunk) {
goto out;
}
}
}
/* XXX NUTEMPO fix this to not use samples */
for (vector<MidiSourceLockMap*>::iterator m = midi_source_locks.begin(); m != midi_source_locks.end(); ++m) {
const MidiBuffer& buf = buffers.get_midi(0);
for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
Evoral::Event<samplepos_t> ev = *i;
if (!endpoint || for_export) {
ev.set_time(ev.time() - position);
} else {
ev.set_time(ev.time() + out_pos - position);
}
(*m)->src->append_event_samples ((*m)->lock, ev, (*m)->src->natural_position().samples());
}
}
out_pos += this_chunk;
}
tracker.resolve_notes (resolved, end-1);
if (!resolved.empty()) {
for (vector<MidiSourceLockMap*>::iterator m = midi_source_locks.begin(); m != midi_source_locks.end(); ++m) {
for (MidiBuffer::iterator i = resolved.begin(); i != resolved.end(); ++i) {
Evoral::Event<samplepos_t> ev = *i;
if (!endpoint || for_export) {
ev.set_time(ev.time() - position);
} else {
ev.set_time(ev.time() + out_pos - position);
}
(*m)->src->append_event_samples ((*m)->lock, ev, (*m)->src->natural_position().samples());
}
}
}
for (vector<MidiSourceLockMap*>::iterator m = midi_source_locks.begin(); m != midi_source_locks.end(); ++m) {
delete *m;
}
midi_source_locks.clear ();
if (!itt.cancel) {
PropertyList plist;
time_t now;
struct tm* xnow;
time (&now);
xnow = localtime (&now);
for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src) {
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
boost::shared_ptr<MidiSource> ms;
if (afs) {
afs->update_header (position, *xnow, now);
afs->flush_header ();
afs->mark_immutable ();
plist.add (Properties::start, timepos_t (0));
} else if ((ms = boost::dynamic_pointer_cast<MidiSource>(*src))) {
Source::WriterLock lock (ms->mutex());
ms->mark_streaming_write_completed(lock);
plist.add (Properties::start, timepos_t (Beats()));
}
}
/* construct a whole-file region to represent the bounced material */
plist.add (Properties::whole_file, true);
plist.add (Properties::length, len); //ToDo: in nutempo, if the Range is snapped to bbt, this should be in bbt (?)
plist.add (Properties::name, region_name_from_path (srcs.front()->name(), true));
plist.add (Properties::tags, "(bounce)");
result = RegionFactory::create (srcs, plist, true);
result->set_name(legal_playlist_name); /*setting name in the properties didn't seem to work, but this does*/
}
out:
if (!result) {
for (vector<boost::shared_ptr<Source> >::iterator src = srcs.begin(); src != srcs.end(); ++src) {
(*src)->mark_for_remove ();
(*src)->drop_references ();
}
} else {
for (vector<boost::shared_ptr<Source> >::iterator src = srcs.begin(); src != srcs.end(); ++src) {
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
if (afs)
afs->done_with_peakfile_writes ();
}
}
_bounce_processing_active = false;
if (need_block_size_reset) {
_engine.main_thread()->drop_buffers ();
track.set_block_size (get_block_size());
}
unblock_processing ();
return result;
}
gain_t*
Session::gain_automation_buffer() const
{
return ProcessThread::gain_automation_buffer ();
}
gain_t*
Session::trim_automation_buffer() const
{
return ProcessThread::trim_automation_buffer ();
}
gain_t*
Session::send_gain_automation_buffer() const
{
return ProcessThread::send_gain_automation_buffer ();
}
gain_t*
Session::scratch_automation_buffer() const
{
return ProcessThread::scratch_automation_buffer ();
}
pan_t**
Session::pan_automation_buffer() const
{
return ProcessThread::pan_automation_buffer ();
}
BufferSet&
Session::get_silent_buffers (ChanCount count)
{
return ProcessThread::get_silent_buffers (count);
}
BufferSet&
Session::get_scratch_buffers (ChanCount count, bool silence)
{
return ProcessThread::get_scratch_buffers (count, silence);
}
BufferSet&
Session::get_noinplace_buffers (ChanCount count)
{
return ProcessThread::get_noinplace_buffers (count);
}
BufferSet&
Session::get_route_buffers (ChanCount count, bool silence)
{
return ProcessThread::get_route_buffers (count, silence);
}
BufferSet&
Session::get_mix_buffers (ChanCount count)
{
return ProcessThread::get_mix_buffers (count);
}
uint32_t
Session::ntracks () const
{
/* XXX Could be optimized by caching */
uint32_t n = 0;
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::const_iterator i = r->begin(); i != r->end(); ++i) {
if (boost::dynamic_pointer_cast<Track> (*i)) {
++n;
}
}
return n;
}
uint32_t
Session::naudiotracks () const
{
/* XXX Could be optimized by caching */
uint32_t n = 0;
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::const_iterator i = r->begin(); i != r->end(); ++i) {
if (boost::dynamic_pointer_cast<AudioTrack> (*i)) {
++n;
}
}
return n;
}
uint32_t
Session::nbusses () const
{
uint32_t n = 0;
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::const_iterator i = r->begin(); i != r->end(); ++i) {
if (boost::dynamic_pointer_cast<Track>(*i) == 0) {
++n;
}
}
return n;
}
uint32_t
Session::nstripables (bool with_monitor) const
{
uint32_t rv = routes.reader()->size ();
rv += _vca_manager->vcas ().size ();
if (with_monitor) {
return rv;
}
if (_monitor_out) {
assert (rv > 0);
--rv;
}
return rv;
}
bool
Session::plot_process_graph (std::string const& file_name) const {
return _graph_chain ? _graph_chain->plot (file_name) : false;
}
void
Session::add_automation_list(AutomationList *al)
{
automation_lists[al->id()] = al;
}
/** @return true if there is at least one record-enabled track, otherwise false */
bool
Session::have_rec_enabled_track () const
{
return g_atomic_int_get (&_have_rec_enabled_track) == 1;
}
bool
Session::have_rec_disabled_track () const
{
return g_atomic_int_get (&_have_rec_disabled_track) == 1;
}
/** Update the state of our rec-enabled tracks flag */
void
Session::update_route_record_state ()
{
boost::shared_ptr<RouteList> rl = routes.reader ();
RouteList::iterator i = rl->begin();
while (i != rl->end ()) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr && tr->rec_enable_control()->get_value()) {
break;
}
++i;
}
int const old = g_atomic_int_get (&_have_rec_enabled_track);
g_atomic_int_set (&_have_rec_enabled_track, i != rl->end () ? 1 : 0);
if (g_atomic_int_get (&_have_rec_enabled_track) != old) {
RecordStateChanged (); /* EMIT SIGNAL */
}
for (i = rl->begin(); i != rl->end (); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (tr && !tr->rec_enable_control()->get_value()) {
break;
}
}
g_atomic_int_set (&_have_rec_disabled_track, i != rl->end () ? 1 : 0);
bool record_arm_state_changed = (old != g_atomic_int_get (&_have_rec_enabled_track) );
if (record_status() == Recording && record_arm_state_changed ) {
RecordArmStateChanged ();
}
UpdateRouteRecordState ();
}
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) {
(*i)->listen_position_changed ();
}
}
void
Session::solo_control_mode_changed ()
{
if (soloing() || listening()) {
if (loading()) {
/* We can't use ::clear_all_solo_state() here because during
session loading at program startup, that will queue a call
to rt_clear_all_solo_state() that will not execute until
AFTER solo states have been established (thus throwing away
the session's saved solo state). So just explicitly turn
them all off.
*/
set_controls (route_list_to_control_list (get_routes(), &Stripable::solo_control), 0.0, Controllable::NoGroup);
} else {
clear_all_solo_state (get_routes());
}
}
}
/** Called when a property of one of our route groups changes */
void
Session::route_group_property_changed (RouteGroup* rg)
{
RouteGroupPropertyChanged (rg); /* EMIT SIGNAL */
}
/** Called when a route is added to one of our route groups */
void
Session::route_added_to_route_group (RouteGroup* rg, boost::weak_ptr<Route> r)
{
RouteAddedToRouteGroup (rg, r);
}
/** Called when a route is removed from one of our route groups */
void
Session::route_removed_from_route_group (RouteGroup* rg, boost::weak_ptr<Route> r)
{
update_route_record_state ();
RouteRemovedFromRouteGroup (rg, r); /* EMIT SIGNAL */
if (!rg->has_control_master () && !rg->has_subgroup () && rg->empty()) {
remove_route_group (*rg);
}
}
boost::shared_ptr<AudioTrack>
Session::get_nth_audio_track (uint32_t nth) const
{
boost::shared_ptr<RouteList> rl = routes.reader ();
rl->sort (Stripable::Sorter ());
for (RouteList::const_iterator r = rl->begin(); r != rl->end(); ++r) {
boost::shared_ptr<AudioTrack> at = boost::dynamic_pointer_cast<AudioTrack> (*r);
if (!at) {
continue;
}
if (nth-- == 0) {
return at;
}
}
return boost::shared_ptr<AudioTrack> ();
}
boost::shared_ptr<RouteList>
Session::get_tracks () const
{
boost::shared_ptr<RouteList> rl = routes.reader ();
boost::shared_ptr<RouteList> tl (new RouteList);
for (RouteList::const_iterator r = rl->begin(); r != rl->end(); ++r) {
if (boost::dynamic_pointer_cast<Track> (*r)) {
assert (!(*r)->is_auditioner()); // XXX remove me
tl->push_back (*r);
}
}
return tl;
}
boost::shared_ptr<RouteList>
Session::get_routes_with_regions_at (timepos_t const & p) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
boost::shared_ptr<RouteList> rl (new RouteList);
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
if (!tr) {
continue;
}
boost::shared_ptr<Playlist> pl = tr->playlist ();
if (!pl) {
continue;
}
if (pl->has_region_at (p)) {
rl->push_back (*i);
}
}
return rl;
}
void
Session::goto_end ()
{
if (_session_range_location) {
request_locate (_session_range_location->end().samples(), false, MustStop);
} else {
request_locate (0, MustStop);
}
}
void
Session::goto_start (bool and_roll)
{
if (_session_range_location) {
request_locate (_session_range_location->start().samples(), false, and_roll ? MustRoll : RollIfAppropriate);
} else {
request_locate (0, false, and_roll ? MustRoll : RollIfAppropriate);
}
}
samplepos_t
Session::current_start_sample () const
{
return _session_range_location ? _session_range_location->start().samples() : 0;
}
samplepos_t
Session::current_end_sample () const
{
return _session_range_location ? _session_range_location->end().samples() : 0;
}
timepos_t
Session::current_start () const
{
return _session_range_location ? _session_range_location->start() : timepos_t::max (Temporal::AudioTime);
}
timepos_t
Session::current_end () const
{
return _session_range_location ? _session_range_location->end() : timepos_t::max (Temporal::AudioTime);
}
void
Session::step_edit_status_change (bool yn)
{
bool send = false;
bool val = false;
if (yn) {
send = (_step_editors == 0);
val = true;
_step_editors++;
} else {
send = (_step_editors == 1);
val = false;
if (_step_editors > 0) {
_step_editors--;
}
}
if (send) {
StepEditStatusChange (val);
}
}
void
Session::start_time_changed (samplepos_t old)
{
/* Update the auto loop range to match the session range
(unless the auto loop range has been changed by the user)
*/
Location* s = _locations->session_range_location ();
if (s == 0) {
return;
}
Location* l = _locations->auto_loop_location ();
if (l && l->start() == old && l->end() > s->start()) {
l->set_start (s->start(), true);
}
set_dirty ();
}
void
Session::end_time_changed (samplepos_t old)
{
/* Update the auto loop range to match the session range
(unless the auto loop range has been changed by the user)
*/
Location* s = _locations->session_range_location ();
if (s == 0) {
return;
}
Location* l = _locations->auto_loop_location ();
if (l && l->end() == old && l->start () < s->end()) {
l->set_end (s->end(), true);
}
set_dirty ();
}
std::vector<std::string>
Session::source_search_path (DataType type) const
{
Searchpath sp;
if (session_dirs.size() == 1) {
switch (type) {
case DataType::AUDIO:
sp.push_back (_session_dir->sound_path());
break;
case DataType::MIDI:
sp.push_back (_session_dir->midi_path());
break;
}
} else {
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
SessionDirectory sdir (i->path);
switch (type) {
case DataType::AUDIO:
sp.push_back (sdir.sound_path());
break;
case DataType::MIDI:
sp.push_back (sdir.midi_path());
break;
}
}
}
if (type == DataType::AUDIO) {
const string sound_path_2X = _session_dir->sound_path_2X();
if (Glib::file_test (sound_path_2X, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_DIR)) {
if (find (sp.begin(), sp.end(), sound_path_2X) == sp.end()) {
sp.push_back (sound_path_2X);
}
}
}
// now check the explicit (possibly user-specified) search path
switch (type) {
case DataType::AUDIO:
sp += Searchpath(config.get_audio_search_path ());
break;
case DataType::MIDI:
sp += Searchpath(config.get_midi_search_path ());
break;
}
return sp;
}
void
Session::ensure_search_path_includes (const string& path, DataType type)
{
Searchpath sp;
if (path == ".") {
return;
}
switch (type) {
case DataType::AUDIO:
sp += Searchpath(config.get_audio_search_path ());
break;
case DataType::MIDI:
sp += Searchpath (config.get_midi_search_path ());
break;
}
for (vector<std::string>::iterator i = sp.begin(); i != sp.end(); ++i) {
/* No need to add this new directory if it has the same inode as
an existing one; checking inode rather than name prevents duplicated
directories when we are using symlinks.
On Windows, I think we could just do if (*i == path) here.
*/
if (PBD::equivalent_paths (*i, path)) {
return;
}
}
sp += path;
switch (type) {
case DataType::AUDIO:
config.set_audio_search_path (sp.to_string());
break;
case DataType::MIDI:
config.set_midi_search_path (sp.to_string());
break;
}
}
void
Session::remove_dir_from_search_path (const string& dir, DataType type)
{
Searchpath sp;
switch (type) {
case DataType::AUDIO:
sp = Searchpath(config.get_audio_search_path ());
break;
case DataType::MIDI:
sp = Searchpath (config.get_midi_search_path ());
break;
}
sp -= dir;
switch (type) {
case DataType::AUDIO:
config.set_audio_search_path (sp.to_string());
break;
case DataType::MIDI:
config.set_midi_search_path (sp.to_string());
break;
}
}
boost::shared_ptr<Speakers>
Session::get_speakers()
{
return _speakers;
}
list<string>
Session::unknown_processors () const
{
list<string> p;
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
list<string> t = (*i)->unknown_processors ();
copy (t.begin(), t.end(), back_inserter (p));
}
p.sort ();
p.unique ();
return p;
}
list<string>
Session::missing_filesources (DataType dt) const
{
list<string> p;
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
if (dt == DataType::AUDIO && 0 != boost::dynamic_pointer_cast<SilentFileSource> (i->second)) {
p.push_back (i->second->name());
}
else if (dt == DataType::MIDI && 0 != boost::dynamic_pointer_cast<SMFSource> (i->second) && (i->second->flags() & Source::Missing) != 0) {
p.push_back (i->second->name());
}
}
p.sort ();
return p;
}
void
Session::initialize_latencies ()
{
block_processing ();
Port::set_engine_ratio (_base_sample_rate, AudioEngine::instance()->sample_rate ());
update_latency (false);
update_latency (true);
unblock_processing ();
}
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;
}
void
Session::update_send_delaylines ()
{
/* called in rt-thread, if send latency changed */
_update_send_delaylines = true;
}
bool
Session::update_route_latency (bool playback, bool apply_to_delayline, bool* delayline_update_needed)
{
/* apply_to_delayline can no be called concurrently with processing
* caller must hold process lock when apply_to_delayline == true */
assert (!apply_to_delayline || !AudioEngine::instance()->process_lock().trylock());
DEBUG_TRACE (DEBUG::LatencyCompensation , string_compose ("update_route_latency: %1 apply_to_delayline? %2)\n", (playback ? "PLAYBACK" : "CAPTURE"), (apply_to_delayline ? "yes" : "no")));
/* 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, delayline_update_needed))) {
changed = true;
}
_worst_route_latency = std::max (l, _worst_route_latency);
}
if (_send_latency_changes > 0) {
/* One extra iteration might be needed since we allow u level of aux-sends.
* Except mixbus that allows up to 3 (aux-sends, sends to mixbusses 1-8, sends to mixbusses 9-12,
* and then there's JACK */
if (++bailout < 5) {
cerr << "restarting Session::update_latency. # of send changes: " << _send_latency_changes << " iteration: " << bailout << endl;
goto restart;
}
}
DEBUG_TRACE (DEBUG::LatencyCompensation , string_compose ("update_route_latency: worst proc latency: %1 (changed? %2) recursions: %3\n", _worst_route_latency, (changed ? "yes" : "no"), bailout));
return changed;
}
void
Session::set_owned_port_public_latency (bool playback)
{
/* special routes or IO or ports owned by the session */
if (auditioner) {
samplecnt_t latency = auditioner->set_private_port_latencies (playback);
auditioner->set_public_port_latencies (latency, playback, true);
}
if (_click_io) {
_click_io->set_public_port_latencies (_click_io->connected_latency (playback), playback);
}
boost::shared_ptr<IOPlugList> iop (_io_plugins.reader ());
for (auto const& i : *iop) {
i->set_public_latency (playback);
}
if (_midi_ports) {
_midi_ports->set_public_latency (playback);
}
}
void
Session::update_latency (bool playback)
{
/* called only from AudioEngine::latency_callback.
* but may indirectly be triggered from
* Session::update_latency_compensation -> _engine.update_latencies
*/
DEBUG_TRACE (DEBUG::LatencyCompensation, string_compose ("Engine latency callback: %1 (initial/deletion: %2 adding: %3 deletion: %4)\n",
(playback ? "PLAYBACK" : "CAPTURE"),
inital_connect_or_deletion_in_progress(),
_adding_routes_in_progress,
_route_deletion_in_progress
));
if (inital_connect_or_deletion_in_progress () || _adding_routes_in_progress || _route_deletion_in_progress) {
_engine.queue_latency_update (playback);
return;
}
if (!_engine.running() || _exporting) {
return;
}
#ifndef NDEBUG
Timing t;
#endif
/* Session::new_midi_track -> Route::add_processors -> Delivery::configure_io
* -> IO::ensure_ports -> PortManager::register_output_port
* may run currently (adding many ports) while the backend
* already emits AudioEngine::latency_callback() for previously
* added ports.
*
* Route::set_public_port_latencies() -> IO::latency may try
* to lookup ports that don't yet exist.
* IO::* uses BLOCK_PROCESS_CALLBACK to prevent concurrency,
* so the same has to be done here to prevent a race.
*/
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock (), Glib::Threads::TRY_LOCK);
if (!lm.locked()) {
/* IO::ensure_ports() calls jack_port_register() while holding the process-lock,
* JACK2 may block and call JACKAudioBackend::_latency_callback() which
* ends up here. https://pastebin.com/mitGBwpq
*
* This is a stopgap to be able to use 6.0 with JACK2's insane threading.
* Yes JACK can also concurrently process (using the old graph) yet emit
* a latency-callback (for which we do need the lock).
*
* One alternative is to use _adding_routes_in_progress and
* call graph_reordered (false); however various entry-points
* to ensure_io don't originate from Session.
*
* Eventually Ardour will probably need to be changed to
* register ports lock-free, and mark those ports as "pending",
* and skip them during process and all other callbacks.
*
* Then clear the pending flags in the rt-process context after
* a port-registraion callback.
*/
DEBUG_TRACE (DEBUG::LatencyCompensation, "Engine latency callback: called with process-lock held. queue for later.\n");
queue_latency_recompute ();
return;
}
/* Note; RouteList is sorted as process-graph */
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 */
RouteList* rl = routes.reader().get();
r.reset (new RouteList (*rl));
reverse (r->begin(), r->end());
}
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
/* private port latency includes plugin and I/O delay,
* but no latency compensation delaylines.
*/
samplecnt_t latency = (*i)->set_private_port_latencies (playback);
/* However we also need to reset the latency of connected external
* ports, since those includes latency compensation delaylines.
*/
(*i)->set_public_port_latencies (latency, playback, false);
}
set_owned_port_public_latency (playback);
if (playback) {
/* Processing needs to be blocked while re-configuring delaylines.
*
* With internal backends, AudioEngine::latency_callback () -> this method
* is called from the main_process_thread (so the lock is not contended).
* However jack2 can concurrently process and reconfigure port latencies.
* -> keep the process-lock.
*/
/* prevent any concurrent latency updates */
Glib::Threads::Mutex::Lock lx (_update_latency_lock);
set_worst_output_latency ();
update_route_latency (true, /*apply_to_delayline*/ true, NULL);
/* release before emitting signals */
lm.release ();
} else {
/* process lock is not needed to update worst-case latency */
lm.release ();
Glib::Threads::Mutex::Lock lx (_update_latency_lock);
set_worst_input_latency ();
update_route_latency (false, false, NULL);
}
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
/* Publish port latency. This includes latency-compensation
* delaylines in the direction of signal flow.
*/
samplecnt_t latency = (*i)->set_private_port_latencies (playback);
(*i)->set_public_port_latencies (latency, playback, true);
}
/* now handle non-route ports that we are responsible for */
set_owned_port_public_latency (playback);
DEBUG_TRACE (DEBUG::LatencyCompensation, "Engine latency callback: DONE\n");
LatencyUpdated (playback); /* EMIT SIGNAL */
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::TopologyTiming)) {
t.update ();
std::cerr << string_compose ("Session::update_latency for %1 took %2ms ; DSP %3 %%\n",
playback ? "playback" : "capture", t.elapsed () / 1000.,
100.0 * t.elapsed () / _engine.usecs_per_cycle ());
}
#endif
}
void
Session::set_worst_output_latency ()
{
if (inital_connect_or_deletion_in_progress ()) {
return;
}
_worst_output_latency = 0;
_io_latency = 0;
if (!_engine.running()) {
return;
}
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
_worst_output_latency = max (_worst_output_latency, (*i)->output()->latency());
_io_latency = max (_io_latency, (*i)->output()->latency() + (*i)->input()->latency());
}
if (_click_io) {
_worst_output_latency = max (_worst_output_latency, _click_io->latency());
}
DEBUG_TRACE (DEBUG::LatencyCompensation, string_compose ("Worst output latency: %1\n", _worst_output_latency));
}
void
Session::set_worst_input_latency ()
{
if (inital_connect_or_deletion_in_progress ()) {
return;
}
_worst_input_latency = 0;
if (!_engine.running()) {
return;
}
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
_worst_input_latency = max (_worst_input_latency, (*i)->input()->latency());
}
DEBUG_TRACE (DEBUG::LatencyCompensation, string_compose ("Worst input latency: %1\n", _worst_input_latency));
}
void
Session::update_latency_compensation (bool force_whole_graph, bool called_from_backend)
{
/* Called to update Ardour's internal latency values and compensation
* planning. Typically case is from within ::graph_reordered()
*/
if (inital_connect_or_deletion_in_progress ()) {
return;
}
/* 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::LatencyCompensation, string_compose ("update_latency_compensation%1.\n", (force_whole_graph ? " of whole graph" : "")));
bool delayline_update_needed = false;
bool some_track_latency_changed = update_route_latency (false, false, &delayline_update_needed);
if (some_track_latency_changed || force_whole_graph) {
/* cannot hold lock while engine initiates a full latency callback */
lx.release ();
/* next call will ask the backend up update its latencies.
*
* The semantics of how the backend does this are not well
* defined (Oct 2019).
*
* In all cases, eventually AudioEngine::latency_callback() is
* invoked, which will call Session::update_latency().
*
* Some backends will do that asynchronously with respect to
* this call. Others (JACK1) will do so synchronously, and in
* those cases this call will return until the backend latency
* callback is complete.
*
* Further, if this is called as part of a backend callback,
* then we have to follow the JACK1 rule that we cannot call
* back into the backend during such a callback (otherwise
* deadlock ensues).
*/
if (!called_from_backend) {
DEBUG_TRACE (DEBUG::LatencyCompensation, "update_latency_compensation: delegate to engine\n");
_engine.update_latencies ();
} else {
DEBUG_TRACE (DEBUG::LatencyCompensation, "update_latency_compensation called from engine, don't call back into engine\n");
}
} else if (delayline_update_needed) {
DEBUG_TRACE (DEBUG::LatencyCompensation, "update_latency_compensation: directly apply to routes\n");
lx.release (); // XXX cannot hold this lock when acquiring process_lock ?!
#ifndef MIXBUS
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock (), Glib::Threads::NOT_LOCK);
#endif
lm.acquire ();
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
(*i)->apply_latency_compensation ();
}
}
DEBUG_TRACE (DEBUG::LatencyCompensation, "update_latency_compensation: complete\n");
}
const std::string
Session::session_name_is_legal (const string& path)
{
static const char illegal_chars[] = { '/', '\\', ':', ';' };
for (size_t i = 0; i < sizeof (illegal_chars); ++i) {
if (path.find (illegal_chars[i]) != string::npos) {
return std::string (1, illegal_chars[i]);
}
}
for (size_t i = 0; i < path.length(); ++i) {
if (iscntrl (path[i])) {
return _("Control Char");
}
}
return std::string ();
}
void
Session::notify_presentation_info_change (PropertyChange const& what_changed)
{
if (deletion_in_progress() || _route_reorder_in_progress) {
return;
}
if (what_changed.contains (Properties::order)) {
PBD::Unwinder<bool> uw (_route_reorder_in_progress, true);
ensure_stripable_sort_order ();
reassign_track_numbers ();
set_dirty ();
}
}
void
Session::controllable_touched (boost::weak_ptr<PBD::Controllable> c)
{
_recently_touched_controllable = c;
}
boost::shared_ptr<PBD::Controllable>
Session::recently_touched_controllable () const
{
return _recently_touched_controllable.lock ();
}
bool
Session::operation_in_progress (GQuark op) const
{
return (find (_current_trans_quarks.begin(), _current_trans_quarks.end(), op) != _current_trans_quarks.end());
}
void
Session::reconnect_ltc_output ()
{
if (_ltc_output_port) {
string src = Config->get_ltc_output_port();
_ltc_output_port->disconnect_all ();
if (src != _("None") && !src.empty()) {
_ltc_output_port->connect (src);
}
}
}
void
Session::set_range_selection (timepos_t const & start, timepos_t const & end)
{
_range_selection = Temporal::Range (start, end);
}
void
Session::set_object_selection (timepos_t const & start, timepos_t const & end)
{
_object_selection = Temporal::Range (start, end);
}
void
Session::clear_range_selection ()
{
_range_selection = Temporal::Range (timepos_t::max (Temporal::AudioTime), timepos_t::max (Temporal::AudioTime));
}
void
Session::clear_object_selection ()
{
_object_selection = Temporal::Range (timepos_t::max (Temporal::AudioTime), timepos_t::max (Temporal::AudioTime));
}
void
Session::cut_copy_section (timepos_t const& start, timepos_t const& end, timepos_t const& to, bool const copy)
{
std::list<TimelineRange> ltr;
TimelineRange tlr (start, end, 0);
ltr.push_back (tlr);
if (copy) {
begin_reversible_command (_("Copy Section"));
} else {
begin_reversible_command (_("Move Section"));
}
{
/* disable DiskReader::playlist_ranges_moved moving automation */
bool automation_follows = Config->get_automation_follows_regions ();
Config->set_automation_follows_regions (false);
for (auto& pl : _playlists->playlists) {
pl->freeze ();
pl->clear_changes ();
pl->clear_owned_changes ();
boost::shared_ptr<Playlist> p = copy ? pl->copy (ltr) : pl->cut (ltr);
// TODO copy interpolated MIDI events
if (!copy) {
pl->ripple (start, end.distance(start), NULL);
}
/* now make space at the insertion-point */
pl->ripple (to, start.distance(end), NULL);
pl->paste (p, to, 1);
vector<Command*> cmds;
pl->rdiff (cmds);
add_commands (cmds);
add_command (new StatefulDiffCommand (pl));
}
for (auto& pl : _playlists->playlists) {
pl->thaw ();
}
Config->set_automation_follows_regions (automation_follows);
}
for (auto& r : *(routes.reader())) {
r->cut_copy_section (start, end, to, copy);
}
// TODO: update ranges and Tempo-Map
if (!abort_empty_reversible_command ()) {
commit_reversible_command ();
}
}
void
Session::auto_connect_route (boost::shared_ptr<Route> route,
bool connect_inputs,
bool connect_outputs,
const ChanCount& input_start,
const ChanCount& output_start,
const ChanCount& input_offset,
const ChanCount& output_offset)
{
Glib::Threads::Mutex::Lock lx (_auto_connect_queue_lock);
DEBUG_TRACE (DEBUG::PortConnectAuto,
string_compose ("Session::auto_connect_route '%1' ci: %2 co: %3 is=(%4) os=(%5) io=(%6) oo=(%7)\n",
route->name(), connect_inputs, connect_outputs,
input_start, output_start, input_offset, output_offset));
_auto_connect_queue.push (AutoConnectRequest (route,
connect_inputs, connect_outputs,
input_start, output_start,
input_offset, output_offset));
lx.release (); // XXX check try-lock + pthread_cond_wait
auto_connect_thread_wakeup ();
}
void
Session::auto_connect_thread_wakeup ()
{
if (pthread_mutex_trylock (&_auto_connect_mutex) == 0) {
pthread_cond_signal (&_auto_connect_cond);
pthread_mutex_unlock (&_auto_connect_mutex);
}
}
void
Session::queue_latency_recompute ()
{
g_atomic_int_inc (&_latency_recompute_pending);
auto_connect_thread_wakeup ();
}
void
Session::auto_connect (const AutoConnectRequest& ar)
{
boost::shared_ptr<Route> route = ar.route.lock();
if (!route) { return; }
if (loading()) {
return;
}
/* If both inputs and outputs are auto-connected to physical ports,
* use the max of input and output offsets to ensure auto-connected
* port numbers always match up (e.g. the first audio input and the
* first audio output of the route will have the same physical
* port number). Otherwise just use the lowest input or output
* offset possible.
*/
const bool in_out_physical =
(Config->get_input_auto_connect() & AutoConnectPhysical)
&& (Config->get_output_auto_connect() & AutoConnectPhysical)
&& ar.connect_inputs;
const ChanCount in_offset = in_out_physical
? ChanCount::max(ar.input_offset, ar.output_offset)
: ar.input_offset;
const ChanCount out_offset = in_out_physical
? ChanCount::max(ar.input_offset, ar.output_offset)
: ar.output_offset;
DEBUG_TRACE (DEBUG::PortConnectAuto,
string_compose ("Session::auto_connect '%1' iop: %2 is=(%3) os=(%4) Eio=(%5) Eoo=(%6)\n",
route->name(), in_out_physical, ar.input_start, ar.output_start, in_offset, out_offset));
for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
vector<string> physinputs;
vector<string> physoutputs;
/* for connecting track inputs we only want MIDI ports marked
* for "music".
*/
get_physical_ports (physinputs, physoutputs, *t, MidiPortMusic);
DEBUG_TRACE (DEBUG::PortConnectAuto,
string_compose ("Physical MidiPortMusic %1 Ports count in: %2 out %3\n",
(*t).to_string(), physinputs.size(), physoutputs.size()));
if (!physinputs.empty() && ar.connect_inputs) {
uint32_t nphysical_in = physinputs.size();
for (uint32_t i = ar.input_start.get(*t); i < route->n_inputs().get(*t) && i < nphysical_in; ++i) {
string port;
if (Config->get_input_auto_connect() & AutoConnectPhysical) {
port = physinputs[(in_offset.get(*t) + i) % nphysical_in];
}
if (!port.empty() && route->input()->connect (route->input()->ports().port(*t, i), port, this)) {
DEBUG_TRACE (DEBUG::PortConnectAuto, "Failed to auto-connect input.");
break;
}
}
}
if (!physoutputs.empty() && ar.connect_outputs) {
DEBUG_TRACE (DEBUG::PortConnectAuto,
string_compose ("Connect %1 outputs # %2 .. %3\n",
(*t).to_string(), ar.output_start.get(*t), route->n_outputs().get(*t)));
uint32_t nphysical_out = physoutputs.size();
for (uint32_t i = ar.output_start.get(*t); i < route->n_outputs().get(*t); ++i) {
string port;
if ((*t) == DataType::MIDI && (Config->get_output_auto_connect() & AutoConnectPhysical)) {
port = physoutputs[(out_offset.get(*t) + i) % nphysical_out];
} else if ((*t) == DataType::AUDIO && (Config->get_output_auto_connect() & AutoConnectMaster)) {
/* master bus is audio only */
if (_master_out && _master_out->n_inputs().get(*t) > 0) {
port = _master_out->input()->ports().port(*t,
i % _master_out->input()->n_ports().get(*t))->name();
}
}
if (!port.empty() && route->output()->connect (route->output()->ports().port(*t, i), port, this)) {
DEBUG_TRACE (DEBUG::PortConnectAuto, "Failed to auto-connect output.");
break;
}
}
}
}
}
void
Session::auto_connect_thread_start ()
{
if (g_atomic_int_get (&_ac_thread_active)) {
return;
}
Glib::Threads::Mutex::Lock lx (_auto_connect_queue_lock);
while (!_auto_connect_queue.empty ()) {
_auto_connect_queue.pop ();
}
lx.release ();
g_atomic_int_set (&_ac_thread_active, 1);
if (pthread_create (&_auto_connect_thread, NULL, auto_connect_thread, this)) {
g_atomic_int_set (&_ac_thread_active, 0);
}
}
void
Session::auto_connect_thread_terminate ()
{
if (!g_atomic_int_get (&_ac_thread_active)) {
return;
}
{
Glib::Threads::Mutex::Lock lx (_auto_connect_queue_lock);
while (!_auto_connect_queue.empty ()) {
_auto_connect_queue.pop ();
}
}
/* cannot use auto_connect_thread_wakeup() because that is allowed to
* fail to wakeup the thread.
*/
pthread_mutex_lock (&_auto_connect_mutex);
g_atomic_int_set (&_ac_thread_active, 0);
pthread_cond_signal (&_auto_connect_cond);
pthread_mutex_unlock (&_auto_connect_mutex);
void *status;
pthread_join (_auto_connect_thread, &status);
}
void *
Session::auto_connect_thread (void *arg)
{
Session *s = static_cast<Session *>(arg);
pthread_set_name (X_("autoconnect"));
s->auto_connect_thread_run ();
pthread_exit (0);
return 0;
}
void
Session::auto_connect_thread_run ()
{
SessionEvent::create_per_thread_pool (X_("autoconnect"), 1024);
PBD::notify_event_loops_about_thread_creation (pthread_self(), X_("autoconnect"), 1024);
pthread_mutex_lock (&_auto_connect_mutex);
Glib::Threads::Mutex::Lock lx (_auto_connect_queue_lock);
while (g_atomic_int_get (&_ac_thread_active)) {
if (!_auto_connect_queue.empty ()) {
/* Why would we need the process lock?
*
* A: if ports are added while connections change,
* the backend's iterator may be invalidated:
* graph_order_callback() -> resort_routes() -> direct_feeds_according_to_reality () -> backend::connected_to()
* Ardour::IO uses the process-lock to avoid concurrency, too
*/
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
while (!_auto_connect_queue.empty ()) {
const AutoConnectRequest ar (_auto_connect_queue.front());
_auto_connect_queue.pop ();
lx.release ();
auto_connect (ar);
lx.acquire ();
}
}
lx.release ();
if (!actively_recording ()) { // might not be needed,
/* this is only used for updating plugin latencies, the
* graph does not change. so it's safe in general.
* BUT..
* update_latency_compensation ()
* calls DiskWriter::set_capture_offset () which
* modifies the capture-offset, which can be a problem.
*/
while (g_atomic_int_and (&_latency_recompute_pending, 0)) {
update_latency_compensation (false, false);
if (g_atomic_int_get (&_latency_recompute_pending)) {
Glib::usleep (1000);
}
}
}
if (_midi_ports && g_atomic_int_get (&_update_pretty_names)) {
boost::shared_ptr<Port> ap = boost::dynamic_pointer_cast<Port> (vkbd_output_port ());
if (ap->pretty_name () != _("Virtual Keyboard")) {
ap->set_pretty_name (_("Virtual Keyboard"));
}
g_atomic_int_set (&_update_pretty_names, 0);
}
if (_engine.port_deletions_pending ().read_space () > 0) {
// this may call ARDOUR::Port::drop ... jack_port_unregister ()
// jack1 cannot cope with removing ports while processing
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
_engine.clear_pending_port_deletions ();
}
lx.acquire ();
if (_auto_connect_queue.empty ()) {
lx.release ();
pthread_cond_wait (&_auto_connect_cond, &_auto_connect_mutex);
lx.acquire ();
}
}
lx.release ();
pthread_mutex_unlock (&_auto_connect_mutex);
}
void
Session::cancel_all_solo ()
{
StripableList sl;
get_stripables (sl);
set_controls (stripable_list_to_control_list (sl, &Stripable::solo_control), 0.0, Controllable::NoGroup);
clear_all_solo_state (routes.reader());
_engine.monitor_port().clear_ports (false);
}
bool
Session::listening () const
{
if (_listen_cnt > 0) {
return true;
}
if (_monitor_out && _engine.monitor_port().monitoring ()) {
return true;
}
return false;
}
void
Session::maybe_update_tempo_from_midiclock_tempo (float bpm)
{
TempoMap::WritableSharedPtr tmap (TempoMap::write_copy());
if (tmap->n_tempos() == 1) {
Temporal::TempoMetric const & metric (tmap->metric_at (0));
if (fabs (metric.tempo().note_types_per_minute() - bpm) > (0.01 * metric.tempo().note_types_per_minute())) {
tmap->change_tempo (metric.get_editable_tempo(), Tempo (bpm, 4.0, bpm));
TempoMap::update (tmap);
return;
}
}
TempoMap::abort_update ();
}
void
Session::send_mclk_for_cycle (samplepos_t start_sample, samplepos_t end_sample, pframes_t n_samples, samplecnt_t pre_roll)
{
midi_clock->tick (start_sample, end_sample, n_samples, pre_roll);
}
void
Session::set_had_destructive_tracks (bool yn)
{
_had_destructive_tracks = yn;
}
bool
Session::had_destructive_tracks() const
{
return _had_destructive_tracks;
}
bool
Session::nth_mixer_scene_valid (size_t nth) const
{
Glib::Threads::RWLock::ReaderLock lm (_mixer_scenes_lock);
if (_mixer_scenes.size () <= nth) {
return false;
}
if (!_mixer_scenes[nth]) {
return false;
}
return !_mixer_scenes[nth]->empty ();
}
bool
Session::apply_nth_mixer_scene (size_t nth)
{
boost::shared_ptr<MixerScene> scene;
{
Glib::Threads::RWLock::ReaderLock lm (_mixer_scenes_lock);
if (_mixer_scenes.size () <= nth) {
return false;
}
if (!_mixer_scenes[nth]) {
return false;
}
scene = _mixer_scenes[nth];
}
assert (scene);
_last_touched_mixer_scene_idx = nth;
return scene->apply ();
}
bool
Session::apply_nth_mixer_scene (size_t nth, RouteList const& rl)
{
boost::shared_ptr<MixerScene> scene;
{
Glib::Threads::RWLock::ReaderLock lm (_mixer_scenes_lock);
if (_mixer_scenes.size () <= nth) {
return false;
}
if (!_mixer_scenes[nth]) {
return false;
}
scene = _mixer_scenes[nth];
}
assert (scene);
ControllableSet acs;
for (auto const& r : rl) {
r->automatables (acs);
}
_last_touched_mixer_scene_idx = nth;
return scene->apply (acs);
}
void
Session::store_nth_mixer_scene (size_t nth)
{
boost::shared_ptr<MixerScene> scn = nth_mixer_scene (nth, true);
_last_touched_mixer_scene_idx = nth;
scn->snapshot ();
//calling code is expected to set a name, but we need to initalize with 'something'
if (scn->name().length()==0) {
std::string str = Glib::DateTime::create_now_local().format ("%FT%H.%M.%S");
scn->set_name(str);
}
}
boost::shared_ptr<MixerScene>
Session::nth_mixer_scene (size_t nth, bool create_if_missing)
{
Glib::Threads::RWLock::ReaderLock lm (_mixer_scenes_lock);
if (create_if_missing) {
if (_mixer_scenes.size() > nth && _mixer_scenes[nth]) {
return _mixer_scenes[nth];
}
lm.release ();
Glib::Threads::RWLock::WriterLock lw (_mixer_scenes_lock);
if (_mixer_scenes.size() <= nth) {
_mixer_scenes.resize (nth + 1);
}
_mixer_scenes[nth] = boost::shared_ptr<MixerScene> (new MixerScene (*this));
return _mixer_scenes[nth];
}
if (_mixer_scenes.size () <= nth) {
return boost::shared_ptr<MixerScene> ();
}
return _mixer_scenes[nth];
}
std::vector<boost::shared_ptr<MixerScene>>
Session::mixer_scenes () const
{
Glib::Threads::RWLock::ReaderLock lm (_mixer_scenes_lock);
return _mixer_scenes;
}
Session::ProcessorChangeBlocker::ProcessorChangeBlocker (Session* s, bool rc)
: _session (s)
, _reconfigure_on_delete (rc)
{
g_atomic_int_inc (&s->_ignore_route_processor_changes);
}
Session::ProcessorChangeBlocker::~ProcessorChangeBlocker ()
{
if (g_atomic_int_dec_and_test (&_session->_ignore_route_processor_changes)) {
gint type = g_atomic_int_and (&_session->_ignored_a_processor_change, 0);
if (_reconfigure_on_delete) {
if (type & RouteProcessorChange::GeneralChange) {
_session->route_processors_changed (RouteProcessorChange ());
} else {
if (type & RouteProcessorChange::MeterPointChange) {
_session->route_processors_changed (RouteProcessorChange (RouteProcessorChange::MeterPointChange));
}
if (type & RouteProcessorChange::RealTimeChange) {
_session->route_processors_changed (RouteProcessorChange (RouteProcessorChange::RealTimeChange));
}
}
}
}
}
void
Session::foreach_route (void (Route::*method)())
{
for (auto & r : *(routes.reader())) {
((r.get())->*method) ();
}
}