#include "ardour/recent_sessions.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"
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);
, _post_transport_work (0)
, _send_timecode_update (false)
, _all_route_group (new RouteGroup (*this, "all"))
- , route_graph (new Graph(*this))
+ , _process_graph (new Graph (*this))
, routes (new RouteList)
, _total_free_4k_blocks (0)
, _bundles (new BundleList)
Session::~Session ()
{
+#ifdef PT_TIMING
+ ST.dump ("ST.dump");
+#endif
destroy ();
}
uint32_t limit = _master_out->n_outputs().n_total();
for (uint32_t n = 0; n < limit; ++n) {
- Port* p = _master_out->output()->nth (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 (_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);
+ 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();
for (uint32_t n = 0; n < limit; ++n) {
- Port* p = _monitor_out->output()->ports().port(DataType::AUDIO, 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];
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);
+ //_process_graph->dump(1);
#ifndef NDEBUG
boost::shared_ptr<RouteList> rl = routes.reader ();
#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.
+
+ Note: the process graph rechain does not require a
+ topologically-sorted list, but hey ho.
+ */
+ _process_graph->rechain (sorted_routes, edges);
+ _current_route_graph = edges;
- RouteSorter cmp;
- r->sort (cmp);
+ /* 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);
+ }
- route_graph->rechain (r);
+ 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".
*/
list<boost::shared_ptr<MidiTrack> >
Session::new_midi_track (TrackMode mode, RouteGroup* route_group, uint32_t how_many, string name_template)
control_id = ntracks() + nbusses();
- 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 ());
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 ());
}
#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 ());
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;
while (how_many) {
- XMLNode node_copy (*node); // make a copy so we can change the name if we need to
+ XMLNode node_copy (*node);
- std::string node_name = IO::name_from_state (*node_copy.children().front());
+ /* Remove IDs of everything so that new ones are used */
+ node_copy.remove_property_recursively (X_("id"));
- /* 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*/
- }
-
- /* 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) {
*/
resort_routes ();
- route_graph->clear_other_chain ();
+ _process_graph->clear_other_chain ();
/* get rid of it from the dead wood collection in the route list manager */
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)
{
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;
search_path = config.get_midi_search_path ();
break;
}
-
+
split (search_path, dirs, ':');
for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
}
}
+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;
+}