#include "pbd/file_utils.h"
#include "pbd/convert.h"
#include "pbd/strsplit.h"
+#include "pbd/strsplit.h"
+#include "pbd/unwind.h"
#include "ardour/amp.h"
#include "ardour/analyser.h"
#include "ardour/audio_track.h"
#include "ardour/audioengine.h"
#include "ardour/audiofilesource.h"
-#include "ardour/audioplaylist.h"
-#include "ardour/audioregion.h"
#include "ardour/auditioner.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/configuration.h"
#include "ardour/control_protocol_manager.h"
-#include "ardour/crossfade.h"
-#include "ardour/cycle_timer.h"
#include "ardour/data_type.h"
#include "ardour/debug.h"
#include "ardour/filename_extensions.h"
-#include "ardour/internal_send.h"
-#include "ardour/io_processor.h"
-#include "ardour/midi_diskstream.h"
-#include "ardour/midi_playlist.h"
-#include "ardour/midi_region.h"
+#include "ardour/graph.h"
#include "ardour/midi_track.h"
#include "ardour/midi_ui.h"
#include "ardour/named_selection.h"
-#include "ardour/process_thread.h"
+#include "ardour/operations.h"
#include "ardour/playlist.h"
+#include "ardour/plugin.h"
#include "ardour/plugin_insert.h"
-#include "ardour/port_insert.h"
-#include "ardour/processor.h"
+#include "ardour/process_thread.h"
#include "ardour/rc_configuration.h"
#include "ardour/recent_sessions.h"
+#include "ardour/region.h"
#include "ardour/region_factory.h"
-#include "ardour/return.h"
+#include "ardour/route_graph.h"
#include "ardour/route_group.h"
#include "ardour/send.h"
#include "ardour/session.h"
#include "ardour/session_directory.h"
-#include "ardour/session_directory.h"
-#include "ardour/session_metadata.h"
#include "ardour/session_playlists.h"
-#include "ardour/slave.h"
#include "ardour/smf_source.h"
#include "ardour/source_factory.h"
-#include "ardour/tape_file_matcher.h"
-#include "ardour/tempo.h"
#include "ardour/utils.h"
-#include "ardour/graph.h"
-#include "ardour/speakers.h"
-#include "ardour/operations.h"
#include "midi++/port.h"
+#include "midi++/jack_midi_port.h"
#include "midi++/mmc.h"
#include "midi++/manager.h"
#include "i18n.h"
+namespace ARDOUR {
+class MidiSource;
+class Processor;
+class Speakers;
+}
+
using namespace std;
using namespace ARDOUR;
using namespace PBD;
PBD::Signal1<void, framepos_t> Session::StartTimeChanged;
PBD::Signal1<void, framepos_t> Session::EndTimeChanged;
-PBD::Signal0<void> Session::AutoBindingOn;
-PBD::Signal0<void> Session::AutoBindingOff;
PBD::Signal2<void,std::string, std::string> 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;
static void clean_up_session_event (SessionEvent* ev) { delete ev; }
const SessionEvent::RTeventCallback Session::rt_cleanup (clean_up_session_event);
+/** @param snapshot_name Snapshot name, without .ardour prefix */
Session::Session (AudioEngine &eng,
const string& fullpath,
const string& snapshot_name,
, _post_transport_work (0)
, _send_timecode_update (false)
, _all_route_group (new RouteGroup (*this, "all"))
- , route_graph (new Graph(*this))
, routes (new RouteList)
, _total_free_4k_blocks (0)
, _bundles (new BundleList)
, click_data (0)
, click_emphasis_data (0)
, main_outs (0)
- , _metadata (new SessionMetadata())
, _have_rec_enabled_track (false)
, _suspend_timecode_transmission (0)
{
_locations = new Locations (*this);
+ if (how_many_dsp_threads () > 1) {
+ /* For now, only create the graph if we are using >1 DSP threads, as
+ it is a bit slower than the old code with 1 thread.
+ */
+ _process_graph.reset (new Graph (*this));
+ }
+
playlists.reset (new SessionPlaylists);
_all_route_group->set_active (true, this);
Session::~Session ()
{
+#ifdef PT_TIMING
+ ST.dump ("ST.dump");
+#endif
destroy ();
}
delete *i;
}
- Crossfade::set_buffer_size (0);
-
/* not strictly necessary, but doing it here allows the shared_ptr debugging to work */
playlists.reset ();
/* every time we reconnect, recompute worst case output latencies */
- _engine.Running.connect_same_thread (*this, boost::bind (&Session::set_worst_io_latencies, this));
+ _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this));
if (synced_to_jack()) {
_engine.transport_stop ();
XMLNode* child = 0;
_click_io.reset (new ClickIO (*this, "click"));
+ _click_gain.reset (new Amp (*this));
+ _click_gain->activate ();
if (state_tree && (child = find_named_node (*state_tree->root(), "Click")) != 0) {
/* existing state for Click */
- int c;
+ int c = 0;
if (Stateful::loading_state_version < 3000) {
c = _click_io->set_state_2X (*child->children().front(), Stateful::loading_state_version, false);
} else {
- c = _click_io->set_state (*child->children().front(), Stateful::loading_state_version);
+ 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 ();
BootMessage (_("Setup signal flow and plugins"));
+ /* Reset all panners */
+
+ Delivery::reset_panners ();
+
+ /* this will cause the CPM to instantiate any protocols that are in use
+ * (or mandatory), which will pass it this Session, and then call
+ * set_state() on each instantiated protocol to match stored state.
+ */
+
ControlProtocolManager::instance().set_session (this);
/* This must be done after the ControlProtocolManager set_session above,
as it will set states for ports which the ControlProtocolManager creates.
*/
+
MIDI::Manager::instance()->set_port_states (Config->midi_port_states ());
/* And this must be done after the MIDI::Manager::set_port_states as
hookup_io ();
- if (_is_new && !no_auto_connect()) {
+ /* Let control protocols know that we are now all connected, so they
+ * could start talking to surfaces if they want to.
+ */
+
+ ControlProtocolManager::instance().midi_connectivity_established ();
+ if (_is_new && !no_auto_connect()) {
Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock());
+ auto_connect_master_bus ();
+ }
- /* don't connect the master bus outputs if there is a monitor bus */
+ _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty));
- if (_master_out && Config->get_auto_connect_standard_busses() && !_monitor_out) {
+ /* update latencies */
- /* if requested auto-connect the outputs to the first N physical ports.
- */
+ initialize_latencies ();
- uint32_t limit = _master_out->n_outputs().n_total();
+ /* hook us up to the engine */
- for (uint32_t n = 0; n < limit; ++n) {
- Port* p = _master_out->output()->nth (n);
- string connect_to;
- if (outputs[p->type()].size() > n) {
- connect_to = outputs[p->type()][n];
- }
+ BootMessage (_("Connect to engine"));
+ _engine.set_session (this);
+}
- 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;
- }
- }
+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;
}
}
+ }
+}
- if (_monitor_out) {
+void
+Session::remove_monitor_section ()
+{
+ if (!_monitor_out) {
+ return;
+ }
- /* AUDIO ONLY as of june 29th 2009, because listen semantics for anything else
- are undefined, at best.
- */
+ /* force reversion to Solo-In-Place */
+ Config->set_solo_control_is_listen_control (false);
- /* control out listens to master bus (but ignores it
- under some conditions)
- */
+ {
+ /* Hold process lock while doing this so that we don't hear bits and
+ * pieces of audio as we work on each route.
+ */
+
+ Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
+
+ /* Connect tracks to monitor section. Note that in an
+ existing session, the internal sends will already exist, but we want the
+ routes to notice that they connect to the control out specifically.
+ */
+
+
+ boost::shared_ptr<RouteList> r = routes.reader ();
+ PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+
+ for (RouteList::iterator x = r->begin(); x != r->end(); ++x) {
+
+ if ((*x)->is_monitor()) {
+ /* relax */
+ } else if ((*x)->is_master()) {
+ /* relax */
+ } else {
+ (*x)->remove_aux_or_listen (_monitor_out);
+ }
+ }
+ }
- uint32_t limit = _monitor_out->n_inputs().n_audio();
+ remove_route (_monitor_out);
+ auto_connect_master_bus ();
+}
- if (_master_out) {
- for (uint32_t n = 0; n < limit; ++n) {
- AudioPort* p = _monitor_out->input()->ports().nth_audio_port (n);
- AudioPort* o = _master_out->output()->ports().nth_audio_port (n);
+void
+Session::add_monitor_section ()
+{
+ RouteList rl;
- 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_out || !_master_out) {
+ return;
+ }
- /* if control out is not connected, connect control out to physical outs
- */
+ boost::shared_ptr<Route> r (new Route (*this, _("monitor"), Route::MonitorOut, DataType::AUDIO));
- if (!_monitor_out->output()->connected ()) {
+ if (r->init ()) {
+ return;
+ }
- if (!Config->get_monitor_bus_preferred_bundle().empty()) {
+#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
+ // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
+#endif
+ {
+ Glib::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);
+ }
- boost::shared_ptr<Bundle> b = bundle_by_name (Config->get_monitor_bus_preferred_bundle());
+ rl.push_back (r);
+ add_routes (rl, false, false, false);
+
+ assert (_monitor_out);
- if (b) {
- _monitor_out->output()->connect_ports_to_bundle (b, this);
- } else {
- warning << string_compose (_("The preferred I/O for the monitor bus (%1) cannot be found"),
- Config->get_monitor_bus_preferred_bundle())
- << endmsg;
- }
+ /* 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() && !_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, 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 */
- } else {
+ uint32_t mod = n_physical_outputs.get (DataType::AUDIO);
+ uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO);
+ vector<string> outputs[DataType::num_types];
- /* Monitor bus is audio only */
- uint32_t mod = n_physical_outputs.get (DataType::AUDIO);
- uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO);
-
- if (mod != 0) {
-
- for (uint32_t n = 0; n < limit; ++n) {
-
- 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;
- }
- }
+ for (uint32_t i = 0; i < DataType::num_types; ++i) {
+ _engine.get_physical_outputs (DataType (DataType::Symbol (i)), outputs[i]);
+ }
+
+
+ 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;
}
}
}
}
}
- _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty));
+ /* Hold process lock while doing this so that we don't hear bits and
+ * pieces of audio as we work on each route.
+ */
+
+ Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
- /* hook us up to the engine */
+ /* Connect tracks to monitor section. Note that in an
+ existing session, the internal sends will already exist, but we want the
+ routes to notice that they connect to the control out specifically.
+ */
- BootMessage (_("Connect to engine"));
- _engine.set_session (this);
- update_latency_compensation (true);
+ boost::shared_ptr<RouteList> rls = routes.reader ();
+
+ PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+
+ for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) {
+
+ if ((*x)->is_monitor()) {
+ /* relax */
+ } else if ((*x)->is_master()) {
+ /* relax */
+ } else {
+ (*x)->enable_monitor_send ();
+ }
+ }
}
void
/* Tell all IO objects to connect themselves together */
- cerr << "Enable IO connections, state = " << _state_of_the_state << endl;
-
IO::enable_connecting ();
- MIDI::Port::MakeConnections ();
-
- /* Now reset all panners */
-
- Delivery::reset_panners ();
-
- /* Connect tracks to monitor/listen bus if there is one. Note that in an
- existing session, the internal sends will already exist, but we want the
- routes to notice that they connect to the control out specifically.
- */
-
- if (_monitor_out) {
- boost::shared_ptr<RouteList> r = routes.reader ();
- for (RouteList::iterator x = r->begin(); x != r->end(); ++x) {
-
- if ((*x)->is_monitor()) {
-
- /* relax */
-
- } else if ((*x)->is_master()) {
-
- /* relax */
-
- } else {
-
- (*x)->listen_via_monitor ();
- }
- }
- }
+ MIDI::JackMIDIPort::MakeConnections ();
/* Anyone who cares about input state, wake up and do something */
boost::shared_ptr<AudioTrack> tr = boost::dynamic_pointer_cast<AudioTrack> (*i);
if (tr && tr->record_enabled ()) {
//cerr << "switching to input = " << !auto_input << __FILE__ << __LINE__ << endl << endl;
- tr->monitor_input (yn);
+ tr->request_jack_monitors_input (yn);
}
}
}
-void
-Session::reset_input_monitor_state ()
-{
- if (transport_rolling()) {
- set_track_monitor_input_status (Config->get_monitoring_model() == HardwareMonitoring && !config.get_auto_input());
- } else {
- set_track_monitor_input_status (Config->get_monitoring_model() == HardwareMonitoring);
- }
-}
-
void
Session::auto_punch_start_changed (Location* location)
{
if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
set_track_monitor_input_status (false);
}
+
+ RecordStateChanged (); /* emit signal */
}
}
}
}
-struct RouteSorter {
- /** @return true to run r1 before r2, otherwise false */
- bool sort_by_rec_enabled (const boost::shared_ptr<Route>& r1, const boost::shared_ptr<Route>& r2) {
- if (r1->record_enabled()) {
- if (r2->record_enabled()) {
- /* both rec-enabled, just use signal order */
- return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
- } else {
- /* r1 rec-enabled, r2 not rec-enabled, run r2 early */
- return false;
- }
- } else {
- if (r2->record_enabled()) {
- /* r2 rec-enabled, r1 not rec-enabled, run r1 early */
- return true;
- } else {
- /* neither rec-enabled, use signal order */
- return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
- }
- }
- }
-
- bool operator() (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> r2) {
- if (r2->feeds (r1)) {
- /* r1 fed by r2; run r2 early */
- return false;
- } else if (r1->feeds (r2)) {
- /* r2 fed by r1; run r1 early */
- return true;
- } else {
- if (r1->not_fed ()) {
- if (r2->not_fed ()) {
- /* no ardour-based connections inbound to either route. */
- return sort_by_rec_enabled (r1, r2);
- } else {
- /* r2 has connections, r1 does not; run r1 early */
- return true;
- }
- } else {
- if (r2->not_fed()) {
- /* r1 has connections, r2 does not; run r2 early */
- return false;
- } else {
- /* both r1 and r2 have connections, but not to each other. just use signal order */
- return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
- }
- }
- }
- }
-};
static void
trace_terminal (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> rbase)
Session::resort_routes ()
{
/* don't do anything here with signals emitted
- by Routes while we are being destroyed.
+ by Routes during initial setup or while we
+ are being destroyed.
*/
- if (_state_of_the_state & Deletion) {
+ if (_state_of_the_state & (InitialConnecting | Deletion)) {
return;
}
/* writer goes out of scope and forces update */
}
- //route_graph->dump(1);
-
#ifndef NDEBUG
boost::shared_ptr<RouteList> rl = routes.reader ();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
#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)
{
- RouteList::iterator i, j;
-
- for (i = r->begin(); i != r->end(); ++i) {
+ /* We are going to build a directed graph of our routes;
+ this is where the edges of that graph are put.
+ */
+
+ GraphEdges edges;
+
+ /* Go through all routes doing two things:
+ *
+ * 1. Collect the edges of the route graph. Each of these edges
+ * is a pair of routes, one of which directly feeds the other
+ * either by a JACK connection or by an internal send.
+ *
+ * 2. Begin the process of making routes aware of which other
+ * routes directly or indirectly feed them. This information
+ * is used by the solo code.
+ */
+
+ for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+ /* Clear out the route's list of direct or indirect feeds */
(*i)->clear_fed_by ();
- for (j = r->begin(); j != r->end(); ++j) {
-
- /* although routes can feed themselves, it will
- cause an endless recursive descent if we
- detect it. so don't bother checking for
- self-feeding.
- */
-
- if (*j == *i) {
- continue;
- }
+ for (RouteList::iterator j = r->begin(); j != r->end(); ++j) {
bool via_sends_only;
- if ((*j)->direct_feeds (*i, &via_sends_only)) {
+ /* See if this *j feeds *i according to the current state of the JACK
+ connections and internal sends.
+ */
+ if ((*j)->direct_feeds_according_to_reality (*i, &via_sends_only)) {
+ /* add the edge to the graph (part #1) */
+ edges.add (*j, *i, via_sends_only);
+ /* tell the route (for part #2) */
(*i)->add_fed_by (*j, via_sends_only);
}
}
}
- for (i = r->begin(); i != r->end(); ++i) {
- trace_terminal (*i, *i);
- }
+ /* Attempt a topological sort of the route graph */
+ boost::shared_ptr<RouteList> sorted_routes = topological_sort (r, edges);
+
+ if (sorted_routes) {
+ /* We got a satisfactory topological sort, so there is no feedback;
+ use this new graph.
- RouteSorter cmp;
- r->sort (cmp);
+ Note: the process graph rechain does not require a
+ topologically-sorted list, but hey ho.
+ */
+ if (_process_graph) {
+ _process_graph->rechain (sorted_routes, edges);
+ }
+
+ _current_route_graph = edges;
- route_graph->rechain (r);
+ /* Complete the building of the routes' lists of what directly
+ or indirectly feeds them.
+ */
+ for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+ trace_terminal (*i, *i);
+ }
+
+ r = sorted_routes;
#ifndef NDEBUG
- DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n");
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 signal order %2\n",
- (*i)->name(), (*i)->order_key ("signal")));
- }
+ DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n");
+ for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+ DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 signal order %2\n",
+ (*i)->name(), (*i)->order_key ("signal")));
+ }
#endif
+ SuccessfulGraphSort (); /* EMIT SIGNAL */
+
+ } else {
+ /* 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. Note also that we do not
+ do trace_terminal here, as it would fail due to an endless recursion,
+ so the solo code will think that everything is still connected
+ as it was before.
+ */
+
+ FeedbackDetected (); /* EMIT SIGNAL */
+ }
+
}
/** Find a route name starting with \a base, maybe followed by the
}
/** Caller must not hold process lock
- * @param name_template string to use for the start of the name, or "" to use "Midi".
+ * @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 (TrackMode mode, RouteGroup* route_group, uint32_t how_many, string name_template)
+Session::new_midi_track (boost::shared_ptr<PluginInfo> instrument, TrackMode mode, RouteGroup* route_group, uint32_t how_many, string name_template)
{
char track_name[32];
uint32_t track_id = 0;
list<boost::shared_ptr<MidiTrack> > ret;
uint32_t control_id;
- control_id = ntracks() + nbusses();
+ control_id = next_control_id ();
- bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Midi");
+ bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("MIDI");
while (how_many) {
- if (!find_route_name (name_template.empty() ? _("Midi") : name_template, ++track_id, track_name, sizeof(track_name), use_number)) {
+ if (!find_route_name (name_template.empty() ? _("MIDI") : name_template, ++track_id, track_name, sizeof(track_name), use_number)) {
error << "cannot find name for new midi track" << endmsg;
goto failed;
}
track->use_new_diskstream();
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
- boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
+ // boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
#endif
{
Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
failed:
if (!new_routes.empty()) {
- add_routes (new_routes, true, true);
+ add_routes (new_routes, true, true, true);
+
+ if (instrument) {
+ for (RouteList::iterator r = new_routes.begin(); r != new_routes.end(); ++r) {
+ PluginPtr plugin = instrument->load (*this);
+ boost::shared_ptr<Processor> p (new PluginInsert (*this, plugin));
+ (*r)->add_processor (p, PreFader);
+
+ }
+ }
}
return ret;
* @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, TrackMode mode, RouteGroup* route_group, uint32_t how_many, string name_template
- )
+Session::new_audio_track (int input_channels, int output_channels, TrackMode mode, RouteGroup* route_group,
+ uint32_t how_many, string name_template)
{
char track_name[32];
uint32_t track_id = 0;
list<boost::shared_ptr<AudioTrack> > ret;
uint32_t control_id;
- control_id = ntracks() + nbusses() + 1;
+ control_id = next_control_id ();
bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Audio");
track->use_new_diskstream();
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
- boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
+ // boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
#endif
{
Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
failed:
if (!new_routes.empty()) {
- add_routes (new_routes, true, true);
+ add_routes (new_routes, true, true, true);
}
return ret;
RouteList ret;
uint32_t control_id;
- control_id = ntracks() + nbusses() + 1;
+ control_id = next_control_id ();
bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Bus");
}
#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
- boost_debug_shared_ptr_mark_interesting (bus.get(), "Route");
+ // boost_debug_shared_ptr_mark_interesting (bus.get(), "Route");
#endif
{
Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
failure:
if (!ret.empty()) {
- add_routes (ret, false, true);
+ add_routes (ret, false, true, true); // autoconnect outputs only
}
return ret;
RouteList
Session::new_route_from_template (uint32_t how_many, const std::string& template_path)
{
- char name[32];
RouteList ret;
uint32_t control_id;
XMLTree tree;
XMLNode* node = tree.root();
- control_id = ntracks() + nbusses() + 1;
+ IO::disable_connecting ();
- while (how_many) {
+ control_id = next_control_id ();
- XMLNode node_copy (*node); // make a copy so we can change the name if we need to
+ while (how_many) {
- std::string node_name = IO::name_from_state (*node_copy.children().front());
+ XMLNode node_copy (*node);
- /* generate a new name by adding a number to the end of the template name */
- if (!find_route_name (node_name.c_str(), ++number, name, sizeof(name), true)) {
- fatal << _("Session: UINT_MAX routes? impossible!") << endmsg;
- /*NOTREACHED*/
- }
+ /* Remove IDs of everything so that new ones are used */
+ node_copy.remove_property_recursively (X_("id"));
- /* set IO children to use the new name */
- XMLNodeList const & children = node_copy.children ();
- for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
- if ((*i)->name() == IO::state_node_name) {
- IO::set_name_in_state (**i, name);
+ try {
+ 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 */
+ char name[32];
+ if (!find_route_name (route_name.c_str(), ++number, name, sizeof(name), true)) {
+ fatal << _("Session: UINT_MAX routes? impossible!") << endmsg;
+ /*NOTREACHED*/
}
- }
- Track::zero_diskstream_id_in_xml (node_copy);
+ /* set this name in the XML description that we are about to use */
+ Route::set_name_in_state (node_copy, name);
- try {
+ /* 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")) {
+ XMLProperty* role = (*i)->property (X_("role"));
+ if (role && role->value() == X_("Listen")) {
+ (*i)->remove_property (X_("bitslot"));
+ }
+ }
+ }
+
boost::shared_ptr<Route> route (XMLRouteFactory (node_copy, 3000));
if (route == 0) {
out:
if (!ret.empty()) {
- add_routes (ret, true, true);
+ add_routes (ret, true, true, true);
+ IO::enable_connecting ();
}
return ret;
}
void
-Session::add_routes (RouteList& new_routes, bool auto_connect, bool save)
+Session::add_routes (RouteList& new_routes, bool input_auto_connect, bool output_auto_connect, bool save)
{
ChanCount existing_inputs;
ChanCount existing_outputs;
}
}
- if (auto_connect) {
- auto_connect_route (r, existing_inputs, existing_outputs, true);
+ if (input_auto_connect || output_auto_connect) {
+ auto_connect_route (r, existing_inputs, existing_outputs, true, input_auto_connect);
}
}
if (_monitor_out && IO::connecting_legal) {
- for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x) {
- if ((*x)->is_monitor()) {
- /* relax */
- } else if ((*x)->is_master()) {
- /* relax */
- } else {
- (*x)->listen_via_monitor ();
+ {
+ Glib::Mutex::Lock lm (_engine.process_lock());
+
+ for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x) {
+ if ((*x)->is_monitor()) {
+ /* relax */
+ } else if ((*x)->is_master()) {
+ /* relax */
+ } else {
+ (*x)->enable_monitor_send ();
+ }
}
}
void
Session::add_internal_sends (boost::shared_ptr<Route> dest, Placement p, boost::shared_ptr<RouteList> senders)
{
- if (dest->is_monitor() || dest->is_master()) {
+ 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();
+ dest->add_internal_return ();
}
- for (RouteList::iterator i = senders->begin(); i != senders->end(); ++i) {
-
- if ((*i)->is_monitor() || (*i)->is_master() || (*i) == dest) {
- continue;
- }
-
- (*i)->listen_via (dest, p);
- }
+ sender->add_aux_send (dest, before);
graph_reordered ();
}
void
Session::remove_route (boost::shared_ptr<Route> route)
{
- if (((route == _master_out) || (route == _monitor_out)) && !Config->get_allow_special_bus_removal()) {
+ if (route == _master_out) {
return;
}
}
if (route == _monitor_out) {
-
- /* cancel control outs for all routes */
-
- for (RouteList::iterator r = rs->begin(); r != rs->end(); ++r) {
- (*r)->drop_listen (_monitor_out);
- }
-
_monitor_out.reset ();
}
*/
resort_routes ();
- route_graph->clear_other_chain ();
+ if (_process_graph) {
+ _process_graph->clear_other_chain ();
+ }
/* get rid of it from the dead wood collection in the route list manager */
}
void
-Session::set_exclusive_input_active (boost::shared_ptr<Route> rt, bool others_on)
+Session::set_exclusive_input_active (boost::shared_ptr<Route> rt, bool /*others_on*/)
{
RouteList rl;
vector<string> connections;
return boost::shared_ptr<Route> ((Route*) 0);
}
+boost::shared_ptr<Track>
+Session::track_by_diskstream_id (PBD::ID id)
+{
+ boost::shared_ptr<RouteList> r = routes.reader ();
+
+ for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+ boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (*i);
+ if (t && t->using_diskstream_id (id)) {
+ return t;
+ }
+ }
+
+ return boost::shared_ptr<Track> ();
+}
+
boost::shared_ptr<Route>
Session::route_by_remote_id (uint32_t id)
{
/* 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 (!_state_of_the_state & InCleanup) {
+ if (!(_state_of_the_state & InCleanup)) {
/* save state so we don't end up with a session file
referring to non-existent sources.
void
Session::non_realtime_set_audition ()
{
- if (!pending_audition_region) {
- auditioner->audition_current_playlist ();
- } else {
- auditioner->audition_region (pending_audition_region);
- pending_audition_region.reset ();
- }
+ assert (pending_audition_region);
+ auditioner->audition_region (pending_audition_region);
+ pending_audition_region.reset ();
AuditionActive (true); /* EMIT SIGNAL */
}
}
}
+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 = 0; 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 ()
{
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)
{
}
}
+void
+Session::unmark_aux_send_id (uint32_t id)
+{
+ if (id < aux_send_bitset.size()) {
+ aux_send_bitset[id] = false;
+ }
+}
+
void
Session::unmark_return_id (uint32_t id)
{
boost::shared_ptr<Region>
Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
bool /*overwrite*/, vector<boost::shared_ptr<Source> >& srcs,
- InterThreadInfo& itt, bool enable_processing)
+ InterThreadInfo& itt,
+ boost::shared_ptr<Processor> endpoint, bool include_endpoint,
+ bool for_export)
{
boost::shared_ptr<Region> result;
boost::shared_ptr<Playlist> playlist;
goto out;
}
- /* external redirects will be a problem */
-
- if (track.has_external_redirects()) {
- goto out;
- }
-
ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
for (uint32_t chan_n = 0; chan_n < diskstream_channels.n_audio(); ++chan_n) {
srcs.push_back (fsource);
}
- /* tell redirects that care that we are about to use a much larger blocksize */
+ /* 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 (chunk_size);
- /* XXX need to flush all redirects */
-
position = start;
to_do = len;
this_chunk = min (to_do, chunk_size);
- if (track.export_stuff (buffers, start, this_chunk, enable_processing)) {
+ if (track.export_stuff (buffers, start, this_chunk, endpoint, include_endpoint, for_export)) {
goto out;
}
Location* l = _locations->auto_loop_location ();
- if (l->start() == old) {
+ if (l && l->start() == old) {
l->set_start (s->start(), true);
}
}
Location* l = _locations->auto_loop_location ();
- if (l->end() == old) {
+ if (l && l->end() == old) {
l->set_end (s->end(), true);
}
}
string
Session::source_search_path (DataType type) const
{
- string search_path;
+ vector<string> s;
if (session_dirs.size() == 1) {
switch (type) {
case DataType::AUDIO:
- search_path = _session_dir->sound_path().to_string();
+ s.push_back ( _session_dir->sound_path().to_string());
break;
case DataType::MIDI:
- search_path = _session_dir->midi_path().to_string();
+ s.push_back (_session_dir->midi_path().to_string());
break;
}
} else {
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
SessionDirectory sdir (i->path);
- if (!search_path.empty()) {
- search_path += ':';
- }
switch (type) {
case DataType::AUDIO:
- search_path += sdir.sound_path().to_string();
+ s.push_back (sdir.sound_path().to_string());
break;
case DataType::MIDI:
- search_path += sdir.midi_path().to_string();
+ s.push_back (sdir.midi_path().to_string());
break;
}
}
}
- /* now add user-specified locations
+ /* now check the explicit (possibly user-specified) search path
*/
vector<string> dirs;
}
for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
- search_path += ':';
- search_path += *i;
+ vector<string>::iterator si;
+
+ for (si = s.begin(); si != s.end(); ++si) {
+ if ((*si) == *i) {
+ break;
+ }
+ }
+
+ if (si == s.end()) {
+ s.push_back (*i);
+ }
+ }
+
+ string search_path;
+
+ for (vector<string>::iterator si = s.begin(); si != s.end(); ++si) {
+ if (!search_path.empty()) {
+ search_path += ':';
+ }
+ search_path += *si;
}
return search_path;
split (search_path, dirs, ':');
for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
- if (*i == path) {
+ /* 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::sys::inodes_same (*i, path)) {
return;
}
}
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());
- cerr << "\n!!! I JUST REVERSED THE ROUTE LIST (" << r->size() << ")!!!\n\n";
}
/* compute actual latency values for the given direction and store them all in per-port
}
}
+void
+Session::initialize_latencies ()
+{
+ {
+ Glib::Mutex::Lock lm (_engine.process_lock());
+ update_latency (false);
+ update_latency (true);
+ }
+
+ set_worst_io_latencies ();
+}
+
void
Session::set_worst_io_latencies ()
{
DEBUG_TRACE (DEBUG::Latency, string_compose ("worst signal processing latency: %1 (changed ? %2)\n", _worst_track_latency,
(some_track_latency_changed ? "yes" : "no")));
- DEBUG_TRACE(DEBUG::Latency, "---------------------------- DONE update latency compensation\n\n")
+ DEBUG_TRACE(DEBUG::Latency, "---------------------------- DONE update latency compensation\n\n");
+
+ if (some_track_latency_changed || force_whole_graph) {
+ _engine.update_latencies ();
+ }
+
+
+ for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+ if (!tr) {
+ continue;
+ }
+ tr->set_capture_offset ();
+ }
}
+char
+Session::session_name_is_legal (const string& path)
+{
+ char illegal_chars[] = { '/', '\\', ':', ';', '\0' };
+
+ for (int i = 0; illegal_chars[i]; ++i) {
+ if (path.find (illegal_chars[i]) != string::npos) {
+ return illegal_chars[i];
+ }
+ }
+
+ return 0;
+}
+
+uint32_t
+Session::next_control_id () const
+{
+ return ntracks() + nbusses() + 1;
+}