minor but important fix for the wrap-buffer case in a recording audio diskstream
[ardour.git] / libs / ardour / session_state.cc
index 82ee766b2338eb8b73db530b48b9e301b8f95b68..95c59fd5e686619c2ec063d2211f6082302c4753 100644 (file)
@@ -6,7 +6,7 @@
   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,
 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.
@@ -48,6 +48,8 @@
 #include <sys/mount.h>
 #endif
 
+#include <glib.h>
+
 #include <glibmm.h>
 #include <glibmm/thread.h>
 
@@ -69,6 +71,7 @@
 
 #include "ardour/amp.h"
 #include "ardour/audio_diskstream.h"
+#include "ardour/audio_playlist_source.h"
 #include "ardour/audio_track.h"
 #include "ardour/audioengine.h"
 #include "ardour/audiofilesource.h"
 #include "ardour/processor.h"
 #include "ardour/port.h"
 #include "ardour/proxy_controllable.h"
+#include "ardour/recent_sessions.h"
 #include "ardour/region_factory.h"
 #include "ardour/route_group.h"
 #include "ardour/send.h"
@@ -130,6 +134,7 @@ using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 
+
 void
 Session::first_stage_init (string fullpath, string snapshot_name)
 {
@@ -151,12 +156,6 @@ Session::first_stage_init (string fullpath, string snapshot_name)
                _path += G_DIR_SEPARATOR;
        }
 
-       if (Glib::file_test (_path, Glib::FILE_TEST_EXISTS) && ::access (_path.c_str(), W_OK)) {
-               _writable = false;
-       } else {
-               _writable = true;
-       }
-
        /* these two are just provisional settings. set_state()
           will likely override them.
        */
@@ -373,10 +372,6 @@ Session::second_stage_init ()
 
        /* initial program change will be delivered later; see ::config_changed() */
 
-       BootMessage (_("Reset Control Protocols"));
-
-       ControlProtocolManager::instance().set_session (this);
-
        _state_of_the_state = Clean;
 
        Port::set_connecting_blocked (false);
@@ -467,13 +462,6 @@ Session::ensure_subdirs ()
                return -1;
        }
 
-       dir = session_directory().sound_stub_path().to_string();
-
-       if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
-               error << string_compose(_("Session: cannot create session stub sounds dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
-               return -1;
-       }
-
        dir = session_directory().midi_path().to_string();
 
        if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
@@ -481,13 +469,6 @@ Session::ensure_subdirs ()
                return -1;
        }
 
-       dir = session_directory().midi_stub_path().to_string();
-
-       if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
-               error << string_compose(_("Session: cannot create session stub midi dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
-               return -1;
-       }
-
        dir = session_directory().dead_path().to_string();
 
        if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
@@ -532,6 +513,8 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
                return -1;
        }
 
+       _writable = exists_and_writable (sys::path (_path));
+
        if (!mix_template.empty()) {
                std::string in_path = mix_template;
 
@@ -570,7 +553,7 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
        /* set initial start + end point */
 
        _state_of_the_state = Clean;
-        
+
         /* set up Master Out and Control Out if necessary */
 
         if (bus_profile) {
@@ -585,7 +568,7 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
                                 return -1;
                         }
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                       boost_debug_shared_ptr_mark_interesting (rt.get(), "Route");
+                       boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
 #endif
                        {
                                Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
@@ -602,7 +585,7 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
                                         return -1;
                                 }
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                                boost_debug_shared_ptr_mark_interesting (rt, "Route");
+                                boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
 #endif
                                {
                                        Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
@@ -610,7 +593,7 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
                                        r->output()->ensure_io (count, false, this);
                                }
                                 r->set_remote_control_id (control_id);
-                                
+
                                 rl.push_back (r);
                         }
 
@@ -620,7 +603,7 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
                }
 
                if (!rl.empty()) {
-                       add_routes (rl, false);
+                       add_routes (rl, false, false);
                }
 
                 /* this allows the user to override settings with an environment variable.
@@ -630,7 +613,7 @@ Session::create (const string& mix_template, BusProfile* bus_profile)
                         bus_profile->input_ac = AutoConnectOption (0);
                         bus_profile->output_ac = AutoConnectOption (0);
                 }
-                
+
                 Config->set_input_auto_connect (bus_profile->input_ac);
                 Config->set_output_auto_connect (bus_profile->output_ac);
         }
@@ -667,7 +650,8 @@ Session::remove_pending_capture_state ()
 }
 
 /** Rename a state file.
- * @param snapshot_name Snapshot name.
+ *  @param old_name Old snapshot name.
+ *  @param new_name New snapshot name.
  */
 void
 Session::rename_state (string old_name, string new_name)
@@ -695,7 +679,7 @@ Session::rename_state (string old_name, string new_name)
 }
 
 /** Remove a state file.
- * @param snapshot_name Snapshot name.
+ *  @param snapshot_name Snapshot name.
  */
 void
 Session::remove_state (string snapshot_name)
@@ -734,7 +718,7 @@ Session::jack_session_event (jack_session_event_t * event)
         if (event->type == JackSessionSaveTemplate)
         {
                 if (save_template( timebuf )) {
-                        event->flags = JackSessionSaveError; 
+                        event->flags = JackSessionSaveError;
                 } else {
                         string cmd ("ardour3 -P -U ");
                         cmd += event->client_uuid;
@@ -747,7 +731,7 @@ Session::jack_session_event (jack_session_event_t * event)
         else
         {
                 if (save_state (timebuf)) {
-                        event->flags = JackSessionSaveError; 
+                        event->flags = JackSessionSaveError;
                 } else {
                         sys::path xml_path (_session_dir->root_path());
                         xml_path /= legalize_for_path (timebuf) + statefile_suffix;
@@ -772,6 +756,7 @@ Session::jack_session_event (jack_session_event_t * event)
 }
 #endif
 
+/** @param snapshot_name Name to save under, without .ardour / .pending prefix */
 int
 Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot)
 {
@@ -835,7 +820,7 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
 
        } else {
 
-               if (rename (tmp_path.to_string().c_str(), xml_path.to_string().c_str()) != 0) {
+               if (::rename (tmp_path.to_string().c_str(), xml_path.to_string().c_str()) != 0) {
                        error << string_compose (_("could not rename temporary session file %1 to %2"),
                                        tmp_path.to_string(), xml_path.to_string()) << endmsg;
                        sys::remove (tmp_path);
@@ -912,14 +897,7 @@ Session::load_state (string snapshot_name)
 
        set_dirty();
 
-       /* writable() really reflects the whole folder, but if for any
-          reason the session state file can't be written to, still
-          make us unwritable.
-       */
-
-       if (::access (xmlpath.to_string().c_str(), W_OK) != 0) {
-               _writable = false;
-       }
+       _writable = exists_and_writable (xmlpath);
 
        if (!state_tree->read (xmlpath.to_string())) {
                error << string_compose(_("Could not understand ardour file %1"), xmlpath.to_string()) << endmsg;
@@ -950,7 +928,7 @@ Session::load_state (string snapshot_name)
                sscanf (prop->value().c_str(), "%d.%d.%d", &major, &minor, &micro);
                Stateful::loading_state_version = (major * 1000) + minor;
        }
-               
+
        if (Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION) {
 
                sys::path backup_path(_session_dir->root_path());
@@ -1081,20 +1059,23 @@ Session::state(bool full_state)
 
                for (SourceMap::iterator siter = sources.begin(); siter != sources.end(); ++siter) {
 
-                       /* Don't save information about non-destructive file sources that are empty
-                           and unused by any regions.
+                       /* Don't save information about non-file Sources, or
+                        * about non-destructive file sources that are empty
+                        * and unused by any regions.
                         */
 
                        boost::shared_ptr<FileSource> fs;
+
                        if ((fs = boost::dynamic_pointer_cast<FileSource> (siter->second)) != 0) {
+
                                if (!fs->destructive()) {
                                        if (fs->empty() && !fs->used()) {
                                                continue;
                                        }
                                }
-                       }
 
-                       child->add_child_nocopy (siter->second->get_state());
+                               child->add_child_nocopy (siter->second->get_state());
+                       }
                }
        }
 
@@ -1110,6 +1091,22 @@ Session::state(bool full_state)
                                 child->add_child_nocopy (r->state ());
                         }
                 }
+
+               RegionFactory::CompoundAssociations& cassocs (RegionFactory::compound_associations());
+
+               if (!cassocs.empty()) {
+                       XMLNode* ca = node->add_child (X_("CompoundAssociations"));
+
+                       for (RegionFactory::CompoundAssociations::iterator i = cassocs.begin(); i != cassocs.end(); ++i) {
+                               char buf[64];
+                               XMLNode* can = new XMLNode (X_("CompoundAssociation"));
+                               i->first->id().print (buf, sizeof (buf));
+                               can->add_property (X_("copy"), buf);
+                               i->second->id().print (buf, sizeof (buf));
+                               can->add_property (X_("original"), buf);
+                               ca->add_child_nocopy (*can);
+                       }
+               }
        }
 
        if (full_state) {
@@ -1236,8 +1233,6 @@ Session::set_state (const XMLNode& node, int version)
 
        setup_raid_path(_session_dir->root_path().to_string());
 
-       cleanup_stubfiles ();
-       
        if ((prop = node.property (X_("id-counter"))) != 0) {
                uint64_t x;
                sscanf (prop->value().c_str(), "%" PRIu64, &x);
@@ -1258,9 +1253,7 @@ Session::set_state (const XMLNode& node, int version)
 
        IO::disable_connecting ();
 
-       if ((child = find_named_node (node, "Extra")) != 0) {
-               _extra_xml = new XMLNode (*child);
-       }
+       Stateful::save_extra_xml (node);
 
        if (((child = find_named_node (node, "Options")) != 0)) { /* old style */
                load_options (*child);
@@ -1278,6 +1271,24 @@ Session::set_state (const XMLNode& node, int version)
                }
        }
 
+        if ((child = find_named_node (node, X_("Speakers"))) != 0) {
+                _speakers->set_state (*child, version);
+        }
+
+       if ((child = find_named_node (node, "Sources")) == 0) {
+               error << _("Session: XML state has no sources section") << endmsg;
+               goto out;
+       } else if (load_sources (*child)) {
+               goto out;
+       }
+
+       if ((child = find_named_node (node, "TempoMap")) == 0) {
+               error << _("Session: XML state has no Tempo Map section") << endmsg;
+               goto out;
+       } else if (_tempo_map->set_state (*child, version)) {
+               goto out;
+       }
+
        if ((child = find_named_node (node, "Locations")) == 0) {
                error << _("Session: XML state has no locations section") << endmsg;
                goto out;
@@ -1285,10 +1296,6 @@ Session::set_state (const XMLNode& node, int version)
                goto out;
        }
 
-        if ((child = find_named_node (node, X_("Speakers"))) != 0) {
-                _speakers->set_state (*child, version);
-        }
-
        Location* location;
 
        if ((location = _locations->auto_loop_location()) != 0) {
@@ -1308,20 +1315,6 @@ Session::set_state (const XMLNode& node, int version)
                AudioFileSource::set_header_position_offset (_session_range_location->start());
        }
 
-       if ((child = find_named_node (node, "Sources")) == 0) {
-               error << _("Session: XML state has no sources section") << endmsg;
-               goto out;
-       } else if (load_sources (*child)) {
-               goto out;
-       }
-
-       if ((child = find_named_node (node, "TempoMap")) == 0) {
-               error << _("Session: XML state has no Tempo Map section") << endmsg;
-               goto out;
-       } else if (_tempo_map->set_state (*child, version)) {
-               goto out;
-       }
-
        if ((child = find_named_node (node, "Regions")) == 0) {
                error << _("Session: XML state has no Regions section") << endmsg;
                goto out;
@@ -1341,7 +1334,13 @@ Session::set_state (const XMLNode& node, int version)
        } else if (playlists->load_unused (*this, *child)) {
                goto out;
        }
-       
+
+       if ((child = find_named_node (node, "CompoundAssociations")) != 0) {
+               if (load_compounds (*child)) {
+                       goto out;
+               }
+       }
+
        if ((child = find_named_node (node, "NamedSelections")) != 0) {
                if (load_named_selections (*child)) {
                        goto out;
@@ -1359,7 +1358,7 @@ Session::set_state (const XMLNode& node, int version)
                        _bundle_xml_node = new XMLNode (*child);
                }
        }
-       
+
        if (version < 3000) {
                if ((child = find_named_node (node, X_("DiskStreams"))) == 0) {
                        error << _("Session: XML state has no diskstreams section") << endmsg;
@@ -1380,16 +1379,16 @@ Session::set_state (const XMLNode& node, int version)
        _diskstreams_2X.clear ();
 
        if (version >= 3000) {
-               
+
                if ((child = find_named_node (node, "RouteGroups")) == 0) {
                        error << _("Session: XML state has no route groups section") << endmsg;
                        goto out;
                } else if (load_route_groups (*child, version)) {
                        goto out;
                }
-               
+
        } else if (version < 3000) {
-               
+
                if ((child = find_named_node (node, "EditGroups")) == 0) {
                        error << _("Session: XML state has no edit groups section") << endmsg;
                        goto out;
@@ -1444,7 +1443,7 @@ Session::load_routes (const XMLNode& node, int version)
                } else {
                        route = XMLRouteFactory (**niter, version);
                }
-               
+
                if (route == 0) {
                        error << _("Session: cannot create Route from XML description.") << endmsg;
                        return -1;
@@ -1455,7 +1454,7 @@ Session::load_routes (const XMLNode& node, int version)
                new_routes.push_back (route);
        }
 
-       add_routes (new_routes, false);
+       add_routes (new_routes, false, false);
 
        return 0;
 }
@@ -1483,26 +1482,26 @@ Session::XMLRouteFactory (const XMLNode& node, int version)
        if (ds_child) {
 
                boost::shared_ptr<Track> track;
-                
+
                 if (type == DataType::AUDIO) {
                         track.reset (new AudioTrack (*this, X_("toBeResetFroXML")));
                 } else {
                         track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
                 }
-                
+
                 if (track->init()) {
                         return ret;
                 }
-                
+
                 if (track->set_state (node, version)) {
                         return ret;
                 }
-                
+
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
                 boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
 #endif
                 ret = track;
-                
+
        } else {
                boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML")));
 
@@ -1559,28 +1558,28 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version)
                 } else {
                         track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
                 }
-                
+
                 if (track->init()) {
                         return ret;
                 }
-                
+
                 if (track->set_state (node, version)) {
                         return ret;
                 }
 
                track->set_diskstream (*i);
-                
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS                
+
+#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
                 boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
 #endif
                 ret = track;
-                
+
        } else {
                boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML")));
 
                 if (r->init () == 0 && r->set_state (node, version) == 0) {
 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                        boost_debug_shared_ptr_mark_interesting (rt, "Route");
+                        boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
 #endif
                         ret = r;
                 }
@@ -1616,6 +1615,79 @@ Session::load_regions (const XMLNode& node)
        return 0;
 }
 
+int
+Session::load_compounds (const XMLNode& node)
+{
+       XMLNodeList calist = node.children();
+       XMLNodeConstIterator caiter;
+       XMLProperty *caprop;
+
+       for (caiter = calist.begin(); caiter != calist.end(); ++caiter) {
+               XMLNode* ca = *caiter;
+               ID orig_id;
+               ID copy_id;
+
+               if ((caprop = ca->property (X_("original"))) == 0) {
+                       continue;
+               }
+               orig_id = caprop->value();
+
+               if ((caprop = ca->property (X_("copy"))) == 0) {
+                       continue;
+               }
+               copy_id = caprop->value();
+
+               boost::shared_ptr<Region> orig = RegionFactory::region_by_id (orig_id);
+               boost::shared_ptr<Region> copy = RegionFactory::region_by_id (copy_id);
+
+               if (!orig || !copy) {
+                       warning << string_compose (_("Regions in compound description not found (ID's %1 and %2): ignored"),
+                                                  orig_id, copy_id)
+                               << endmsg;
+                       continue;
+               }
+
+               RegionFactory::add_compound_association (orig, copy);
+       }
+
+       return 0;
+}
+
+void
+Session::load_nested_sources (const XMLNode& node)
+{
+       XMLNodeList nlist;
+       XMLNodeConstIterator niter;
+
+       nlist = node.children();
+
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+               if ((*niter)->name() == "Source") {
+
+                       /* it may already exist, so don't recreate it unnecessarily 
+                        */
+
+                       XMLProperty* prop = (*niter)->property (X_("id"));
+                       if (!prop) {
+                               error << _("Nested source has no ID info in session state file! (ignored)") << endmsg;
+                               continue;
+                       }
+
+                       ID source_id (prop->value());
+
+                       if (!source_by_id (source_id)) {
+
+                               try {
+                                       SourceFactory::create (*this, **niter, true);
+                               }
+                               catch (failed_constructor& err) {
+                                       error << string_compose (_("Cannot reconstruct nested source for region %1"), name()) << endmsg;
+                               }
+                       }
+               }
+       }
+}
+
 boost::shared_ptr<Region>
 Session::XMLRegionFactory (const XMLNode& node, bool full)
 {
@@ -1623,6 +1695,15 @@ Session::XMLRegionFactory (const XMLNode& node, bool full)
 
        try {
 
+               const XMLNodeList& nlist = node.children();
+
+               for (XMLNodeConstIterator niter = nlist.begin(); niter != nlist.end(); ++niter) {
+                       XMLNode *child = (*niter);
+                       if (child->name() == "NestedSource") {
+                               load_nested_sources (*child);
+                       }
+               }
+
                 if (!type || type->value() == "audio") {
                         return boost::shared_ptr<Region>(XMLAudioRegionFactory (node, full));
                 } else if (type->value() == "midi") {
@@ -1896,8 +1977,8 @@ Session::load_sources (const XMLNode& node)
                         case 0:
                                 /* user added a new search location, so try again */
                                 goto retry;
-                                
-                                
+
+
                         case 1:
                                 /* user asked to quit the entire session load
                                  */
@@ -2222,7 +2303,7 @@ Session::load_route_groups (const XMLNode& node, int version)
        set_dirty ();
 
        if (version >= 3000) {
-               
+
                for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
                        if ((*niter)->name() == "RouteGroup") {
                                RouteGroup* rg = new RouteGroup (*this, "");
@@ -2307,9 +2388,10 @@ Session::add_route_group (RouteGroup* g)
        _route_groups.push_back (g);
        route_group_added (g); /* EMIT SIGNAL */
 
-       g->MembershipChanged.connect_same_thread (*this, boost::bind (&Session::route_group_changed, this));
-       g->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::route_group_changed, this));
-       
+       g->RouteAdded.connect_same_thread (*this, boost::bind (&Session::route_added_to_route_group, this, _1, _2));
+       g->RouteRemoved.connect_same_thread (*this, boost::bind (&Session::route_removed_from_route_group, this, _1, _2));
+       g->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::route_group_property_changed, this, g));
+
        set_dirty ();
 }
 
@@ -2324,9 +2406,21 @@ Session::remove_route_group (RouteGroup& rg)
 
                route_group_removed (); /* EMIT SIGNAL */
        }
+}
+
+/** Set a new order for our route groups, without adding or removing any.
+ *  @param groups Route group list in the new order.
+ */
+void
+Session::reorder_route_groups (list<RouteGroup*> groups)
+{
+       _route_groups = groups;
 
+       route_groups_reordered (); /* EMIT SIGNAL */
+       set_dirty ();
 }
 
+
 RouteGroup *
 Session::route_group_by_name (string name)
 {
@@ -2371,14 +2465,14 @@ Session::begin_reversible_command (GQuark q)
           to hold all the commands that are committed.  This keeps the order of
           commands correct in the history.
        */
-       
+
        if (_current_trans == 0) {
                /* start a new transaction */
                assert (_current_trans_quarks.empty ());
                _current_trans = new UndoTransaction();
                _current_trans->set_name (g_quark_to_string (q));
        }
-       
+
        _current_trans_quarks.push_front (q);
 }
 
@@ -2387,7 +2481,7 @@ Session::commit_reversible_command (Command *cmd)
 {
        assert (_current_trans);
        assert (!_current_trans_quarks.empty ());
-       
+
        struct timeval now;
 
        if (cmd) {
@@ -2416,16 +2510,12 @@ Session::commit_reversible_command (Command *cmd)
 }
 
 static bool
-accept_all_non_stub_audio_files (const string& path, void */*arg*/)
-{ 
+accept_all_audio_files (const string& path, void */*arg*/)
+{
         if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
                 return false;
         }
 
-        if (FileSource::is_stub_path (path)) {
-                return false;
-        }
-
         if (!AudioFileSource::safe_audio_file_extension (path)) {
                 return false;
         }
@@ -2434,16 +2524,12 @@ accept_all_non_stub_audio_files (const string& path, void */*arg*/)
 }
 
 static bool
-accept_all_non_stub_midi_files (const string& path, void */*arg*/)
+accept_all_midi_files (const string& path, void */*arg*/)
 {
         if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
                 return false;
         }
 
-        if (FileSource::is_stub_path (path)) {
-                return false;
-        }
-
        return ((path.length() > 4 && path.find (".mid") != (path.length() - 4)) ||
                 (path.length() > 4 && path.find (".smf") != (path.length() - 4)) ||
                 (path.length() > 5 && path.find (".midi") != (path.length() - 5)));
@@ -2576,11 +2662,11 @@ Session::cleanup_regions ()
        for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
 
                boost::shared_ptr<AudioRegion> audio_region = boost::dynamic_pointer_cast<AudioRegion>( i->second);
-               
+
                if (!audio_region) {
                        continue;
                }
-               
+
                uint32_t used = playlists->region_use_count (audio_region);
 
                if (used == 0 && !audio_region->automatic()) {
@@ -2616,7 +2702,7 @@ Session::cleanup_sources (CleanupReport& rep)
        _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
 
        /* consider deleting all unused playlists */
-       
+
        if (playlists->maybe_delete_unused (boost::bind (Session::ask_about_playlist_deletion, _1))) {
                ret = 0;
                goto out;
@@ -2686,8 +2772,8 @@ Session::cleanup_sources (CleanupReport& rep)
                i = nexti;
        }
 
-       candidates = scanner (audio_path, accept_all_non_stub_audio_files, (void *) 0, true, true);
-       candidates2 = scanner (midi_path, accept_all_non_stub_midi_files, (void *) 0, true, true);
+       candidates = scanner (audio_path, accept_all_audio_files, (void *) 0, true, true);
+       candidates2 = scanner (midi_path, accept_all_midi_files, (void *) 0, true, true);
 
         /* merge them */
 
@@ -2718,22 +2804,18 @@ Session::cleanup_sources (CleanupReport& rep)
                 ++tmp;
 
                if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) != 0) {
-                        if (!fs->is_stub()) {
-                                if (playlists->source_use_count (fs) != 0) {
-                                        all_sources.insert (fs->path());
-                                } else {
-                                        
-                                        /* we might not remove this source from disk, because it may be used
-                                           by other snapshots, but its not being used in this version
-                                           so lets get rid of it now, along with any representative regions
-                                           in the region list.
-                                        */
-
-                                        cerr << "Source " << i->second->name() << "ID " << i->second->id() << " not used, remove from source list and also all regions\n";
-                                        
-                                        RegionFactory::remove_regions_using_source (i->second);
-                                        sources.erase (i);
-                                }
+                        if (playlists->source_use_count (fs) != 0) {
+                                all_sources.insert (fs->path());
+                        } else {
+
+                                /* we might not remove this source from disk, because it may be used
+                                   by other snapshots, but its not being used in this version
+                                   so lets get rid of it now, along with any representative regions
+                                   in the region list.
+                                */
+
+                                RegionFactory::remove_regions_using_source (i->second);
+                                sources.erase (i);
                         }
                }
 
@@ -2745,18 +2827,18 @@ Session::cleanup_sources (CleanupReport& rep)
 
         if (candidates) {
                 for (vector<string*>::iterator x = candidates->begin(); x != candidates->end(); ++x) {
-                        
+
                         used = false;
                         spath = **x;
-                        
+
                         for (set<string>::iterator i = all_sources.begin(); i != all_sources.end(); ++i) {
-                                
+
                                 if (realpath(spath.c_str(), tmppath1) == 0) {
                                         error << string_compose (_("Cannot expand path %1 (%2)"),
                                                                  spath, strerror (errno)) << endmsg;
                                         continue;
                                 }
-                                
+
                                 if (realpath((*i).c_str(),  tmppath2) == 0) {
                                         error << string_compose (_("Cannot expand path %1 (%2)"),
                                                                  (*i), strerror (errno)) << endmsg;
@@ -2768,7 +2850,7 @@ Session::cleanup_sources (CleanupReport& rep)
                                         break;
                                 }
                         }
-                        
+
                         if (!used) {
                                 unused.push_back (spath);
                         }
@@ -2816,7 +2898,7 @@ Session::cleanup_sources (CleanupReport& rep)
                }
 
                newpath = Glib::build_filename (newpath, Glib::path_get_basename ((*x)));
-                
+
                if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
 
                        /* the new path already exists, try versioning */
@@ -2860,19 +2942,19 @@ Session::cleanup_sources (CleanupReport& rep)
                 */
 
                 string base = basename_nosuffix (*x);
-                base += "%A"; /* this is what we add for the channel suffix of all native files, 
+                base += "%A"; /* this is what we add for the channel suffix of all native files,
                                  or for the first channel of embedded files. it will miss
                                  some peakfiles for other channels
                               */
                string peakpath = peak_path (base);
-                
+
                if (Glib::file_test (peakpath.c_str(), Glib::FILE_TEST_EXISTS)) {
                        if (::unlink (peakpath.c_str()) != 0) {
                                error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
                                                          peakpath, _path, strerror (errno))
                                      << endmsg;
                                /* try to back out */
-                               rename (newpath.c_str(), _path.c_str());
+                               ::rename (newpath.c_str(), _path.c_str());
                                goto out;
                        }
                }
@@ -2911,8 +2993,7 @@ Session::cleanup_trash_sources (CleanupReport& rep)
 
        for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
 
-               dead_dir = (*i).path;
-               dead_dir += dead_dir_name;
+               dead_dir = Glib::build_filename ((*i).path, dead_dir_name);
 
                 clear_directory (dead_dir, &rep.space, &rep.paths);
        }
@@ -2920,45 +3001,6 @@ Session::cleanup_trash_sources (CleanupReport& rep)
        return 0;
 }
 
-void
-Session::cleanup_stubfiles ()
-{
-       vector<space_and_path>::iterator i;
-
-       for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
-
-                string dir;
-                string lname = legalize_for_path (_name);
-
-                vector<string> v;
-
-                /* XXX this is a hack caused by semantic conflicts
-                   between space_and_path and the SessionDirectory concept.
-                */
-
-                v.push_back ((*i).path);
-                v.push_back ("interchange");
-                v.push_back (lname);
-                v.push_back ("audiofiles");
-                v.push_back (stub_dir_name);
-
-                dir = Glib::build_filename (v);
-                
-                clear_directory (dir);
-
-                v.clear ();
-                v.push_back ((*i).path);
-                v.push_back ("interchange");
-                v.push_back (lname);
-                v.push_back ("midifiles");
-                v.push_back (stub_dir_name);
-
-                dir = Glib::build_filename (v);
-
-                clear_directory (dir);
-       }
-}
-
 void
 Session::set_dirty ()
 {
@@ -3067,7 +3109,7 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                r = route_by_remote_id (desc.rid());
                break;
        }
-       
+
        if (!r) {
                return c;
        }
@@ -3088,7 +3130,7 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
        case ControllableDescriptor::Recenable:
        {
                boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(r);
-               
+
                if (t) {
                        c = t->rec_enable_control ();
                }
@@ -3123,17 +3165,17 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                uint32_t parameter_index = desc.target (1);
 
                /* revert to zero based counting */
-               
+
                if (plugin > 0) {
                        --plugin;
                }
-               
+
                if (parameter_index > 0) {
                        --parameter_index;
                }
 
                boost::shared_ptr<Processor> p = r->nth_plugin (plugin);
-               
+
                if (p) {
                        c = boost::dynamic_pointer_cast<ARDOUR::AutomationControl>(
                                p->control(Evoral::Parameter(PluginAutomation, 0, parameter_index)));
@@ -3141,18 +3183,18 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                break;
        }
 
-       case ControllableDescriptor::SendGain: 
+       case ControllableDescriptor::SendGain:
        {
                uint32_t send = desc.target (0);
 
                /* revert to zero-based counting */
-               
+
                if (send > 0) {
                        --send;
                }
-               
+
                boost::shared_ptr<Processor> p = r->nth_send (send);
-               
+
                if (p) {
                        boost::shared_ptr<Send> s = boost::dynamic_pointer_cast<Send>(p);
                        boost::shared_ptr<Amp> a = s->amp();
@@ -3323,7 +3365,7 @@ Session::restore_history (string snapshot_name)
                                } else {
                                        error << _("Failed to downcast MidiSource for SysExDiffCommand") << endmsg;
                                }
-                               
+
                        } else if (n->name() == "PatchChangeDiffCommand") {
 
                                PBD::ID id (n->property("midi-source")->value());
@@ -3367,14 +3409,7 @@ Session::config_changed (std::string p, bool ours)
 
                if (Config->get_monitoring_model() == HardwareMonitoring && transport_rolling()) {
                        /* auto-input only makes a difference if we're rolling */
-
-                       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 && tr->record_enabled ()) {
-                                       tr->monitor_input (!config.get_auto_input());
-                               }
-                       }
+                        set_track_monitor_input_status (!config.get_auto_input());
                }
 
        } else if (p == "punch-in") {
@@ -3419,11 +3454,11 @@ Session::config_changed (std::string p, bool ours)
 
                //poke_midi_thread ();
 
-       } else if (p == "mmc-device-id" || p == "mmc-receive-id") {
+       } else if (p == "mmc-device-id" || p == "mmc-receive-id" || p == "mmc-receive-device-id") {
 
                MIDI::Manager::instance()->mmc()->set_receive_device_id (Config->get_mmc_receive_device_id());
 
-       } else if (p == "mmc-send-id") {
+       } else if (p == "mmc-send-id" || p == "mmc-send-device-id") {
 
                MIDI::Manager::instance()->mmc()->set_send_device_id (Config->get_mmc_send_device_id());
 
@@ -3540,6 +3575,8 @@ Session::config_changed (std::string p, bool ours)
                solo_control_mode_changed ();
        } else if (p == "timecode-offset" || p == "timecode-offset-negative") {
                last_timecode_valid = false;
+       } else if (p == "playback-buffer-seconds") {
+               AudioSource::allocate_working_buffers (frame_rate());
        }
 
        set_dirty ();
@@ -3585,7 +3622,7 @@ void
 Session::setup_midi_machine_control ()
 {
        MIDI::MachineControl* mmc = MIDI::Manager::instance()->mmc ();
-       
+
        mmc->Play.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
        mmc->DeferredPlay.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
        mmc->Stop.connect_same_thread (*this, boost::bind (&Session::mmc_stop, this, _1));
@@ -3618,6 +3655,160 @@ Session::solo_cut_control() const
            it up as a Controllable. Changes to the Controllable will just map back to the RCConfiguration
            parameter.
         */
-        
+
         return _solo_cut_control;
 }
+
+int
+Session::rename (const std::string& new_name)
+{
+       string legal_name = legalize_for_path (new_name);
+       string newpath;
+       string oldstr;
+       string newstr;
+       bool first = true;
+
+#define RENAME ::rename
+
+       /* Rename:
+
+        * session directory
+        * interchange subdirectory
+        * session file
+        * session history
+        
+        * Backup files are left unchanged and not renamed.
+        */
+
+       /* pass one: not 100% safe check that the new directory names don't
+        * already exist ...
+        */
+
+       for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+               vector<string> v;
+
+               oldstr = (*i).path;
+
+               /* this is a stupid hack because Glib::path_get_dirname() is
+                * lexical-only, and so passing it /a/b/c/ gives a different
+                * result than passing it /a/b/c ...
+                */
+
+               if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
+                       oldstr = oldstr.substr (0, oldstr.length() - 1);
+               }
+
+               string base = Glib::path_get_dirname (oldstr);
+               string p = Glib::path_get_basename (oldstr);
+
+               newstr = Glib::build_filename (base, legal_name);
+               
+               if (Glib::file_test (newstr, Glib::FILE_TEST_EXISTS)) {
+                       return -1;
+               }
+       }
+
+       /* Session dirs */
+       
+       for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+               vector<string> v;
+
+               oldstr = (*i).path;
+
+               /* this is a stupid hack because Glib::path_get_dirname() is
+                * lexical-only, and so passing it /a/b/c/ gives a different
+                * result than passing it /a/b/c ...
+                */
+
+               if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
+                       oldstr = oldstr.substr (0, oldstr.length() - 1);
+               }
+
+               string base = Glib::path_get_dirname (oldstr);
+               string p = Glib::path_get_basename (oldstr);
+
+               newstr = Glib::build_filename (base, legal_name);
+
+               cerr << "Rename " << oldstr << " => " << newstr << endl;                
+
+               if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+                       return 1;
+               }
+
+               if (first) {
+                       (*_session_dir) = newstr;
+                       newpath = newstr;
+                       first = 1;
+               }
+
+               /* directory below interchange */
+
+               v.push_back (newstr);
+               v.push_back (interchange_dir_name);
+               v.push_back (p);
+
+               oldstr = Glib::build_filename (v);
+
+               v.clear ();
+               v.push_back (newstr);
+               v.push_back (interchange_dir_name);
+               v.push_back (legal_name);
+
+               newstr = Glib::build_filename (v);
+               
+               cerr << "Rename " << oldstr << " => " << newstr << endl;
+               
+               if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+                       return 1;
+               }
+       }
+
+       /* state file */
+       
+       oldstr = Glib::build_filename (newpath, _current_snapshot_name) + statefile_suffix;
+       newstr= Glib::build_filename (newpath, legal_name) + statefile_suffix;
+       
+       cerr << "Rename " << oldstr << " => " << newstr << endl;                
+
+       if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+               return 1;
+       }
+
+       /* history file */
+
+       
+       oldstr = Glib::build_filename (newpath, _current_snapshot_name) + history_suffix;
+
+       if (Glib::file_test (oldstr, Glib::FILE_TEST_EXISTS))  {
+               newstr = Glib::build_filename (newpath, legal_name) + history_suffix;
+               
+               cerr << "Rename " << oldstr << " => " << newstr << endl;                
+               
+               if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
+                       return 1;
+               }
+       }
+
+       /* remove old name from recent sessions */
+
+       remove_recent_sessions (_path);
+
+       _path = newpath;
+       _current_snapshot_name = new_name;
+       _name = new_name;
+
+       set_dirty ();
+
+       /* save state again to get everything just right */
+
+       save_state (_current_snapshot_name);
+
+
+       /* add to recent sessions */
+
+       store_recent_sessions (new_name, _path);
+
+       return 0;
+
+#undef RENAME
+}