Separate "add master bus" (and add Lua bindings)
[ardour.git] / libs / ardour / session_state.cc
index e7769b719ad695ce0cbabde093046da8115b4a93..051eca4b688d6ed2cab5b331f11b772e03dd4ac3 100644 (file)
@@ -29,6 +29,7 @@
 #include <cerrno>
 #include <cstdio> /* snprintf(3) ... grrr */
 #include <cmath>
+
 #include <unistd.h>
 #include <climits>
 #include <signal.h>
@@ -38,7 +39,7 @@
 #include <sys/vfs.h>
 #endif
 
-#ifdef __APPLE__
+#if defined(__APPLE__) || defined(__FreeBSD__)
 #include <sys/param.h>
 #include <sys/mount.h>
 #endif
@@ -49,6 +50,7 @@
 
 #include <glib.h>
 #include "pbd/gstdio_compat.h"
+#include "pbd/locale_guard.h"
 
 #include <glibmm.h>
 #include <glibmm/threads.h>
 #include "pbd/debug.h"
 #include "pbd/enumwriter.h"
 #include "pbd/error.h"
+#include "pbd/file_archive.h"
 #include "pbd/file_utils.h"
 #include "pbd/pathexpand.h"
 #include "pbd/pthread_utils.h"
 #include "pbd/stacktrace.h"
-#include "pbd/convert.h"
+#include "pbd/types_convert.h"
 #include "pbd/localtime_r.h"
 #include "pbd/unwind.h"
 
@@ -80,6 +83,7 @@
 #include "ardour/audioengine.h"
 #include "ardour/audiofilesource.h"
 #include "ardour/audioregion.h"
+#include "ardour/auditioner.h"
 #include "ardour/automation_control.h"
 #include "ardour/boost_debug.h"
 #include "ardour/butler.h"
@@ -89,6 +93,9 @@
 #include "ardour/filename_extensions.h"
 #include "ardour/graph.h"
 #include "ardour/location.h"
+#ifdef LV2_SUPPORT
+#include "ardour/lv2_plugin.h"
+#endif
 #include "ardour/midi_model.h"
 #include "ardour/midi_patch_manager.h"
 #include "ardour/midi_region.h"
 #include "ardour/playlist_source.h"
 #include "ardour/port.h"
 #include "ardour/processor.h"
+#include "ardour/progress.h"
 #include "ardour/profile.h"
 #include "ardour/proxy_controllable.h"
 #include "ardour/recent_sessions.h"
 #include "ardour/region_factory.h"
+#include "ardour/revision.h"
 #include "ardour/route_group.h"
 #include "ardour/send.h"
+#include "ardour/selection.h"
 #include "ardour/session.h"
 #include "ardour/session_directory.h"
 #include "ardour/session_metadata.h"
 #include "ardour/session_playlists.h"
 #include "ardour/session_state_utils.h"
 #include "ardour/silentfilesource.h"
+#include "ardour/smf_source.h"
 #include "ardour/sndfilesource.h"
 #include "ardour/source_factory.h"
 #include "ardour/speakers.h"
 #include "ardour/template_utils.h"
 #include "ardour/tempo.h"
 #include "ardour/ticker.h"
+#include "ardour/types_convert.h"
 #include "ardour/user_bundle.h"
 #include "ardour/vca.h"
 #include "ardour/vca_manager.h"
 
 #include "LuaBridge/LuaBridge.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 #include <locale.h>
 
 using namespace std;
@@ -173,7 +185,6 @@ Session::pre_engine_init (string fullpath)
        set_next_event ();
        _all_route_group->set_active (true, this);
        interpolation.add_channel_to (0, 0);
-       _vca_manager = new VCAManager (*this);
 
        if (config.get_use_video_sync()) {
                waiting_for_sync_offset = true;
@@ -185,14 +196,14 @@ Session::pre_engine_init (string fullpath)
 
        set_history_depth (Config->get_history_depth());
 
-        /* default: assume simple stereo speaker configuration */
+       /* default: assume simple stereo speaker configuration */
 
-        _speakers->setup_default_speakers (2);
+       _speakers->setup_default_speakers (2);
 
-        _solo_cut_control.reset (new ProxyControllable (_("solo cut control (dB)"), PBD::Controllable::GainLike,
-                                                        boost::bind (&RCConfiguration::set_solo_mute_gain, Config, _1),
-                                                        boost::bind (&RCConfiguration::get_solo_mute_gain, Config)));
-        add_controllable (_solo_cut_control);
+       _solo_cut_control.reset (new ProxyControllable (_("solo cut control (dB)"), PBD::Controllable::GainLike,
+                               boost::bind (&RCConfiguration::set_solo_mute_gain, Config, _1),
+                               boost::bind (&RCConfiguration::get_solo_mute_gain, Config)));
+       add_controllable (_solo_cut_control);
 
        /* These are all static "per-class" signals */
 
@@ -226,16 +237,18 @@ Session::post_engine_init ()
        msc->set_input_port (boost::dynamic_pointer_cast<MidiPort>(scene_input_port()));
        msc->set_output_port (boost::dynamic_pointer_cast<MidiPort>(scene_output_port()));
 
-       boost::function<framecnt_t(void)> timer_func (boost::bind (&Session::audible_frame, this));
+       boost::function<framecnt_t(void)> timer_func (boost::bind (&Session::audible_frame, this, (bool*)(0)));
        boost::dynamic_pointer_cast<AsyncMIDIPort>(scene_input_port())->set_timer (timer_func);
 
        setup_midi_machine_control ();
 
        if (_butler->start_thread()) {
+               error << _("Butler did not start") << endmsg;
                return -1;
        }
 
        if (start_midi_thread ()) {
+               error << _("MIDI I/O thread did not start") << endmsg;
                return -1;
        }
 
@@ -251,8 +264,16 @@ Session::post_engine_init ()
                delete _tempo_map;
                _tempo_map = new TempoMap (_current_frame_rate);
                _tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
-               _tempo_map->MetricPositionChanged.connect_same_thread (*this, boost::bind (&Session::gui_tempo_map_changed, this));
+               _tempo_map->MetricPositionChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
+       } catch (std::exception const & e) {
+               error << _("Unexpected exception during session setup: ") << e.what() << endmsg;
+               return -2;
+       } catch (...) {
+               error << _("Unknown exception during session setup") << endmsg;
+               return -3;
+       }
 
+       try {
                /* MidiClock requires a tempo map */
 
                delete midi_clock;
@@ -263,6 +284,7 @@ Session::post_engine_init ()
 
                SndFileSource::setup_standard_crossfades (*this, frame_rate());
                _engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this));
+               _engine.MidiSelectionPortsChanged.connect_same_thread (*this, boost::bind (&Session::rewire_midi_selection_ports, this));
 
                AudioDiskstream::allocate_working_buffers();
                refresh_disk_space ();
@@ -273,7 +295,8 @@ Session::post_engine_init ()
 
                if (state_tree) {
                        if (set_state (*state_tree->root(), Stateful::loading_state_version)) {
-                               return -1;
+                               error << _("Could not set session state from XML") << endmsg;
+                               return -4;
                        }
                } else {
                        // set_state() will call setup_raid_path(), but if it's a new session we need
@@ -288,7 +311,7 @@ Session::post_engine_init ()
 
                Config->map_parameters (ff);
                config.map_parameters (ft);
-                _butler->map_parameters ();
+               _butler->map_parameters ();
 
                /* Reset all panners */
 
@@ -336,11 +359,14 @@ Session::post_engine_init ()
                _locations->changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this));
 
        } catch (AudioEngine::PortRegistrationFailure& err) {
-               /* handle this one in a different way than all others, so that its clear what happened */
                error << err.what() << endmsg;
-               return -1;
+               return -5;
+       } catch (std::exception const & e) {
+               error << _("Unexpected exception during session setup: ") << e.what() << endmsg;
+               return -6;
        } catch (...) {
-               return -1;
+               error << _("Unknown exception during session setup") << endmsg;
+               return -7;
        }
 
        BootMessage (_("Reset Remote Controls"));
@@ -613,72 +639,36 @@ Session::create (const string& session_template, BusProfile* bus_profile)
        if (Profile->get_trx()) {
 
                /* set initial start + end point : ARDOUR::Session::session_end_shift long.
-                  Remember that this is a brand new session. Sessions
-                  loaded from saved state will get this range from the saved state.
-               */
+                * Remember that this is a brand new session. Sessions
+                * loaded from saved state will get this range from the saved state.
+                */
 
                set_session_range_location (0, 0);
 
                /* Initial loop location, from absolute zero, length 10 seconds  */
 
-               Location* loc = new Location (*this, 0, 10.0 * _engine.sample_rate(), _("Loop"),  Location::IsAutoLoop);
+               Location* loc = new Location (*this, 0, 10.0 * _engine.sample_rate(), _("Loop"),  Location::IsAutoLoop, 0);
                _locations->add (loc, true);
                set_auto_loop_location (loc);
        }
 
        _state_of_the_state = Clean;
 
-        /* set up Master Out and Monitor Out if necessary */
-
-        if (bus_profile) {
+       /* set up Master Out and Monitor Out if necessary */
 
+       if (bus_profile) {
                RouteList rl;
-                ChanCount count(DataType::AUDIO, bus_profile->master_out_channels);
-
-                // Waves Tracks: always create master bus for Tracks
-                if (ARDOUR::Profile->get_trx() || bus_profile->master_out_channels) {
-                       boost::shared_ptr<Route> r (new Route (*this, _("Master"), PresentationInfo::MasterOut, DataType::AUDIO));
-                        if (r->init ()) {
-                                return -1;
-                        }
-
-                        BOOST_MARK_ROUTE(r);
-
-                        {
-                               Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
-                               r->input()->ensure_io (count, false, this);
-                               r->output()->ensure_io (count, false, this);
-                       }
-
-                       rl.push_back (r);
-
-               } else {
-                       /* prohibit auto-connect to master, because there isn't one */
-                       bus_profile->output_ac = AutoConnectOption (bus_profile->output_ac & ~AutoConnectMaster);
-               }
-
-               if (!rl.empty()) {
-                       add_routes (rl, false, false, false, PresentationInfo::max_order);
-               }
-
-               // Waves Tracks: Skip this. Always use autoconnection for Tracks
-               if (!ARDOUR::Profile->get_trx()) {
+               ChanCount count(DataType::AUDIO, bus_profile->master_out_channels);
+               if (bus_profile->master_out_channels) {
+                       int rv = add_master_bus (count);
 
-                       /* this allows the user to override settings with an environment variable.
-                        */
-
-                       if (no_auto_connect()) {
-                               bus_profile->input_ac = AutoConnectOption (0);
-                               bus_profile->output_ac = AutoConnectOption (0);
+                       if (rv) {
+                               return rv;
                        }
 
-                       Config->set_input_auto_connect (bus_profile->input_ac);
-                       Config->set_output_auto_connect (bus_profile->output_ac);
+                       if (Config->get_use_monitor_bus())
+                               add_monitor_section ();
                }
-        }
-
-       if (Config->get_use_monitor_bus() && bus_profile) {
-               add_monitor_section ();
        }
 
        return 0;
@@ -687,9 +677,9 @@ Session::create (const string& session_template, BusProfile* bus_profile)
 void
 Session::maybe_write_autosave()
 {
-        if (dirty() && record_status() != Recording) {
-                save_state("", true);
-        }
+       if (dirty() && record_status() != Recording) {
+               save_state("", true);
+       }
 }
 
 void
@@ -782,11 +772,10 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
        }
        _save_queued = false;
 
-       if (!_engine.connected ()) {
-               error << string_compose (_("the %1 audio engine is not connected and state saving would lose all I/O connections. Session not saved"),
-                                         PROGRAM_NAME)
-                     << endmsg;
-               return 1;
+       snapshot_t fork_state = NormalSave;
+       if (!snapshot_name.empty() && snapshot_name != _current_snapshot_name && !template_only && !pending) {
+               /* snapshot, close midi */
+               fork_state = switch_to_snapshot ? SwitchToSnapshot : SnapshotKeep;
        }
 
 #ifndef NDEBUG
@@ -815,7 +804,7 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
                mark_as_clean = false;
                tree.set_root (&get_template());
        } else {
-               tree.set_root (&get_state());
+               tree.set_root (&state (true, fork_state));
        }
 
        if (snapshot_name.empty()) {
@@ -924,7 +913,7 @@ Session::load_state (string snapshot_name)
 
                /* there is pending state from a crashed capture attempt */
 
-                boost::optional<int> r = AskAboutPendingState();
+               boost::optional<int> r = AskAboutPendingState();
                if (r.get_value_or (1)) {
                        state_was_pending = true;
                }
@@ -937,10 +926,10 @@ Session::load_state (string snapshot_name)
        if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
                xmlpath = Glib::build_filename (_session_dir->root_path(), legalize_for_path (snapshot_name) + statefile_suffix);
                if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
-                        error << string_compose(_("%1: session file \"%2\" doesn't exist!"), _name, xmlpath) << endmsg;
-                        return 1;
-                }
-        }
+                       error << string_compose(_("%1: session file \"%2\" doesn't exist!"), _name, xmlpath) << endmsg;
+                       return 1;
+               }
+       }
 
        state_tree = new XMLTree;
 
@@ -964,22 +953,21 @@ Session::load_state (string snapshot_name)
                return -1;
        }
 
-       XMLProperty const * prop;
-
-       if ((prop = root.property ("version")) == 0) {
-               /* no version implies very old version of Ardour */
-               Stateful::loading_state_version = 1000;
-       } else {
-               if (prop->value().find ('.') != string::npos) {
+       std::string version;
+       if (root.get_property ("version", version)) {
+               if (version.find ('.') != string::npos) {
                        /* old school version format */
-                       if (prop->value()[0] == '2') {
+                       if (version[0] == '2') {
                                Stateful::loading_state_version = 2000;
                        } else {
                                Stateful::loading_state_version = 3000;
                        }
                } else {
-                       Stateful::loading_state_version = atoi (prop->value());
+                       Stateful::loading_state_version = string_to<int32_t>(version);
                }
+       } else {
+               /* no version implies very old version of Ardour */
+               Stateful::loading_state_version = 1000;
        }
 
        if (Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION && _writable) {
@@ -1008,7 +996,6 @@ Session::load_state (string snapshot_name)
 int
 Session::load_options (const XMLNode& node)
 {
-       LocaleGuard lg;
        config.set_variables (node);
        return 0;
 }
@@ -1038,24 +1025,112 @@ Session::get_template()
        return state(false);
 }
 
+typedef std::set<boost::shared_ptr<Playlist> > PlaylistSet;
+typedef std::set<boost::shared_ptr<Source> > SourceSet;
+
+bool
+Session::export_track_state (boost::shared_ptr<RouteList> rl, const string& path)
+{
+       if (Glib::file_test (path, Glib::FILE_TEST_EXISTS))  {
+               return false;
+       }
+       if (g_mkdir_with_parents (path.c_str(), 0755) != 0) {
+               return false;
+       }
+
+       PBD::Unwinder<std::string> uw (_template_state_dir, path);
+
+       LocaleGuard lg;
+       XMLNode* node = new XMLNode("TrackState"); // XXX
+       XMLNode* child;
+
+       PlaylistSet playlists; // SessionPlaylists
+       SourceSet sources;
+
+       // these will work with  new_route_from_template()
+       // TODO: LV2 plugin-state-dir needs to be relative (on load?)
+       child = node->add_child ("Routes");
+       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+               if ((*i)->is_auditioner()) {
+                       continue;
+               }
+               if ((*i)->is_master() || (*i)->is_monitor()) {
+                       continue;
+               }
+               child->add_child_nocopy ((*i)->get_state());
+               boost::shared_ptr<Track> track = boost::dynamic_pointer_cast<Track> (*i);
+               if (track) {
+                       playlists.insert (track->playlist ());
+               }
+       }
+
+       // on load, Regions in the playlists need to resolve and map Source-IDs
+       // also playlist needs to be merged or created with new-name..
+       // ... and Diskstream in tracks adjusted to use the correct playlist
+       child = node->add_child ("Playlists"); // SessionPlaylists::add_state
+       for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
+               child->add_child_nocopy ((*i)->get_state ());
+               boost::shared_ptr<RegionList> prl = (*i)->region_list ();
+               for (RegionList::const_iterator s = prl->begin(); s != prl->end(); ++s) {
+                       const Region::SourceList& sl = (*s)->sources ();
+                       for (Region::SourceList::const_iterator sli = sl.begin(); sli != sl.end(); ++sli) {
+                               sources.insert (*sli);
+                       }
+               }
+       }
+
+       child = node->add_child ("Sources");
+       for (SourceSet::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+               child->add_child_nocopy ((*i)->get_state ());
+               boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (*i);
+               if (fs) {
+#ifdef PLATFORM_WINDOWS
+                       fs->close ();
+#endif
+                       string p = fs->path ();
+                       PBD::copy_file (p, Glib::build_filename (path, Glib::path_get_basename (p)));
+               }
+       }
+
+       std::string sn = Glib::build_filename (path, "share.axml");
+
+       XMLTree tree;
+       tree.set_root (node);
+       return tree.write (sn.c_str());
+}
+
+namespace
+{
+struct route_id_compare {
+       bool
+       operator() (const boost::shared_ptr<Route>& r1, const boost::shared_ptr<Route>& r2)
+       {
+               return r1->id () < r2->id ();
+       }
+};
+} // anon namespace
+
 XMLNode&
-Session::state (bool full_state)
+Session::state (bool full_state, snapshot_t snapshot_type)
 {
        LocaleGuard lg;
        XMLNode* node = new XMLNode("Session");
        XMLNode* child;
 
-       char buf[16];
-       snprintf(buf, sizeof(buf), "%d", CURRENT_SESSION_FILE_VERSION);
-       node->add_property("version", buf);
+       node->set_property("version", CURRENT_SESSION_FILE_VERSION);
+
+       child = node->add_child ("ProgramVersion");
+       child->set_property("created-with", created_with);
+
+       std::string modified_with = string_compose ("%1 %2", PROGRAM_NAME, revision);
+       child->set_property("modified-with", modified_with);
 
        /* store configuration settings */
 
        if (full_state) {
 
-               node->add_property ("name", _name);
-               snprintf (buf, sizeof (buf), "%" PRId64, _base_frame_rate);
-               node->add_property ("sample-rate", buf);
+               node->set_property ("name", _name);
+               node->set_property ("sample-rate", _base_frame_rate);
 
                if (session_dirs.size() > 1) {
 
@@ -1085,25 +1160,22 @@ Session::state (bool full_state)
                        child = node->add_child ("Path");
                        child->add_content (p);
                }
+               node->set_property ("end-is-free", _session_range_end_is_free);
        }
 
        /* save the ID counter */
 
-       snprintf (buf, sizeof (buf), "%" PRIu64, ID::counter());
-       node->add_property ("id-counter", buf);
+       node->set_property ("id-counter", ID::counter());
 
-       snprintf (buf, sizeof (buf), "%u", name_id_counter ());
-       node->add_property ("name-counter", buf);
+       node->set_property ("name-counter", name_id_counter ());
 
        /* save the event ID counter */
 
-       snprintf (buf, sizeof (buf), "%d", Evoral::event_id_counter());
-       node->add_property ("event-counter", buf);
+       node->set_property ("event-counter", Evoral::event_id_counter());
 
        /* save the VCA counter */
 
-       snprintf (buf, sizeof (buf), "%" PRIu32, VCA::get_next_vca_number());
-       node->add_property ("vca-counter", buf);
+       node->set_property ("vca-counter", VCA::get_next_vca_number());
 
        /* various options */
 
@@ -1116,7 +1188,14 @@ Session::state (bool full_state)
                node->add_child_nocopy (*midi_port_stuff);
        }
 
-       node->add_child_nocopy (config.get_variables ());
+       XMLNode& cfgxml (config.get_variables ());
+       if (!full_state) {
+               /* exclude search-paths from template */
+               cfgxml.remove_nodes_and_delete ("name", "audio-search-path");
+               cfgxml.remove_nodes_and_delete ("name", "midi-search-path");
+               cfgxml.remove_nodes_and_delete ("name", "raid-path");
+       }
+       node->add_child_nocopy (cfgxml);
 
        node->add_child_nocopy (ARDOUR::SessionMetadata::Metadata()->get_state());
 
@@ -1130,20 +1209,81 @@ Session::state (bool full_state)
                        /* 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 = boost::dynamic_pointer_cast<FileSource> (siter->second)) == 0) {
+                               continue;
+                       }
+
+                       if (!fs->destructive()) {
+                               if (fs->empty() && !fs->used()) {
+                                       continue;
+                               }
+                       }
+
+                       if (snapshot_type != NormalSave && fs->within_session ()) {
+                               /* copy MIDI sources to new file
+                                *
+                                * We cannot replace the midi-source and MidiRegion::clobber_sources,
+                                * because the GUI (midi_region) has a direct pointer to the midi-model
+                                * of the source, as does UndoTransaction.
+                                *
+                                * On the upside, .mid files are not kept open. The file is only open
+                                * when reading the model initially and when flushing the model to disk:
+                                * source->session_saved () or export.
+                                *
+                                * We can change the _path of the existing source under the hood, keeping
+                                * all IDs, references and pointers intact.
+                                * */
+                               boost::shared_ptr<SMFSource> ms;
+                               if ((ms = boost::dynamic_pointer_cast<SMFSource> (siter->second)) != 0) {
+                                       const std::string ancestor_name = ms->ancestor_name();
+                                       const std::string base          = PBD::basename_nosuffix(ancestor_name);
+                                       const string path               = new_midi_source_path (base, false);
+
+                                       /* use SMF-API to clone data (use the midi_model, not data on disk) */
+                                       boost::shared_ptr<SMFSource> newsrc (new SMFSource (*this, path, SndFileSource::default_writable_flags));
+                                       Source::Lock lm (ms->mutex());
+
+                                       // TODO special-case empty, removable() files: just create a new removable.
+                                       // (load + write flushes the model and creates the file)
+                                       if (!ms->model()) {
+                                               ms->load_model (lm);
+                                       }
+                                       if (ms->write_to (lm, newsrc, Evoral::MinBeats, Evoral::MaxBeats)) {
+                                               error << string_compose (_("Session-Save: Failed to copy MIDI Source '%1' for snapshot"), ancestor_name) << endmsg;
+                                       } else {
+                                               if (snapshot_type == SnapshotKeep) {
+                                                       /* keep working on current session.
+                                                        *
+                                                        * Save snapshot-state with the original filename.
+                                                        * Switch to use new path for future saves of the main session.
+                                                        */
+                                                       child->add_child_nocopy (ms->get_state());
+                                               }
 
-                               if (!fs->destructive()) {
-                                       if (fs->empty() && !fs->used()) {
+                                               /* swap file-paths.
+                                                * ~SMFSource  unlinks removable() files.
+                                                */
+                                               std::string npath (ms->path ());
+                                               ms->replace_file (newsrc->path ());
+                                               newsrc->replace_file (npath);
+
+                                               if (snapshot_type == SwitchToSnapshot) {
+                                                       /* save and switch to snapshot.
+                                                        *
+                                                        * Leave the old file in place (as is).
+                                                        * Snapshot uses new source directly
+                                                        */
+                                                       child->add_child_nocopy (ms->get_state());
+                                               }
                                                continue;
                                        }
                                }
-
-                               child->add_child_nocopy (siter->second->get_state());
                        }
+
+                       child->add_child_nocopy (siter->second->get_state());
                }
        }
 
@@ -1151,18 +1291,18 @@ Session::state (bool full_state)
 
        if (full_state) {
                Glib::Threads::Mutex::Lock rl (region_lock);
-                const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
-                for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
-                        boost::shared_ptr<Region> r = i->second;
-                        /* only store regions not attached to playlists */
-                        if (r->playlist() == 0) {
+               const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
+               for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
+                       boost::shared_ptr<Region> r = i->second;
+                       /* only store regions not attached to playlists */
+                       if (r->playlist() == 0) {
                                if (boost::dynamic_pointer_cast<AudioRegion>(r)) {
                                        child->add_child_nocopy ((boost::dynamic_pointer_cast<AudioRegion>(r))->get_basic_state ());
                                } else {
                                        child->add_child_nocopy (r->get_state ());
                                }
-                        }
-                }
+                       }
+               }
 
                RegionFactory::CompoundAssociations& cassocs (RegionFactory::compound_associations());
 
@@ -1170,29 +1310,27 @@ Session::state (bool full_state)
                        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);
+                               can->set_property (X_("copy"), i->first->id());
+                               can->set_property (X_("original"), i->second->id());
                                ca->add_child_nocopy (*can);
                        }
                }
        }
 
-
-
        if (full_state) {
 
+               node->add_child_nocopy (_selection->get_state());
+
                if (_locations) {
                        node->add_child_nocopy (_locations->get_state());
                }
        } else {
                Locations loc (*this);
+               const bool was_dirty = dirty();
                // for a template, just create a new Locations, populate it
                // with the default start and end, and get the state for that.
-               Location* range = new Location (*this, 0, 0, _("session"), Location::IsSessionRange);
+               Location* range = new Location (*this, 0, 0, _("session"), Location::IsSessionRange, 0);
                range->set (max_framepos, 0);
                loc.add (range);
                XMLNode& locations_state = loc.get_state();
@@ -1206,6 +1344,15 @@ Session::state (bool full_state)
                        }
                }
                node->add_child_nocopy (locations_state);
+
+               /* adding a location above will have marked the session
+                * dirty. This is an artifact, so fix it if the session wasn't
+                * already dirty
+                */
+
+               if (!was_dirty) {
+                       _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
+               }
        }
 
        child = node->add_child ("Bundles");
@@ -1225,17 +1372,11 @@ Session::state (bool full_state)
        {
                boost::shared_ptr<RouteList> r = routes.reader ();
 
-               RoutePublicOrderSorter cmp;
-               RouteList public_order (*r);
-               public_order.sort (cmp);
+               route_id_compare cmp;
+               RouteList xml_node_order (*r);
+               xml_node_order.sort (cmp);
 
-               /* the sort should have put control outs first */
-
-               if (_monitor_out) {
-                       assert (_monitor_out == public_order.front());
-               }
-
-               for (RouteList::iterator i = public_order.begin(); i != public_order.end(); ++i) {
+               for (RouteList::const_iterator i = xml_node_order.begin(); i != xml_node_order.end(); ++i) {
                        if (!(*i)->is_auditioner()) {
                                if (full_state) {
                                        child->add_child_nocopy ((*i)->get_state());
@@ -1269,7 +1410,7 @@ Session::state (bool full_state)
                ltc_output_child->add_child_nocopy (_ltc_output->state (full_state));
        }
 
-        node->add_child_nocopy (_speakers->get_state());
+       node->add_child_nocopy (_speakers->get_state());
        node->add_child_nocopy (_tempo_map->get_state());
        node->add_child_nocopy (get_control_protocol_state());
 
@@ -1292,7 +1433,7 @@ Session::state (bool full_state)
                g_free (b64);
 
                XMLNode* script_node = new XMLNode (X_("Script"));
-               script_node->add_property (X_("lua"), LUA_VERSION);
+               script_node->set_property (X_("lua"), LUA_VERSION);
                script_node->add_content (b64s);
                node->add_child_nocopy (*script_node);
        }
@@ -1313,7 +1454,6 @@ Session::set_state (const XMLNode& node, int version)
        LocaleGuard lg;
        XMLNodeList nlist;
        XMLNode* child;
-       XMLProperty const * prop;
        int ret = -1;
 
        _state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave);
@@ -1323,13 +1463,10 @@ Session::set_state (const XMLNode& node, int version)
                goto out;
        }
 
-       if ((prop = node.property ("name")) != 0) {
-               _name = prop->value ();
-       }
+       node.get_property ("name", _name);
 
-       if ((prop = node.property (X_("sample-rate"))) != 0) {
+       if (node.get_property (X_("sample-rate"), _base_frame_rate)) {
 
-               _base_frame_rate = atoi (prop->value());
                _nominal_frame_rate = _base_frame_rate;
 
                assert (AudioEngine::instance()->running ());
@@ -1341,34 +1478,38 @@ Session::set_state (const XMLNode& node, int version)
                }
        }
 
+       created_with = "unknown";
+       if ((child = find_named_node (node, "ProgramVersion")) != 0) {
+               child->get_property (X_("created-with"), created_with);
+       }
+
        setup_raid_path(_session_dir->root_path());
 
-       if ((prop = node.property (X_("id-counter"))) != 0) {
-               uint64_t x;
-               sscanf (prop->value().c_str(), "%" PRIu64, &x);
-               ID::init_counter (x);
+       node.get_property (X_("end-is-free"), _session_range_end_is_free);
+
+       uint64_t counter;
+       if (node.get_property (X_("id-counter"), counter)) {
+               ID::init_counter (counter);
        } else {
                /* old sessions used a timebased counter, so fake
-                  the startup ID counter based on a standard
-                  timestamp.
-               */
+                * the startup ID counter based on a standard
+                * timestamp.
+                */
                time_t now;
                time (&now);
                ID::init_counter (now);
        }
 
-       if ((prop = node.property (X_("name-counter"))) != 0) {
-               init_name_id_counter (atoi (prop->value()));
+       if (node.get_property (X_("name-counter"), counter)) {
+               init_name_id_counter (counter);
        }
 
-       if ((prop = node.property (X_("event-counter"))) != 0) {
-               Evoral::init_event_id_counter (atoi (prop->value()));
+       if (node.get_property (X_("event-counter"), counter)) {
+               Evoral::init_event_id_counter (counter);
        }
 
-       if ((prop = node.property (X_("vca-counter"))) != 0) {
-               uint32_t x;
-               sscanf (prop->value().c_str(), "%" PRIu32, &x);
-               VCA::set_next_vca_number (x);
+       if (node.get_property (X_("vca-counter"), counter)) {
+               VCA::set_next_vca_number (counter);
        } else {
                VCA::set_next_vca_number (1);
        }
@@ -1397,9 +1538,9 @@ 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, 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;
@@ -1460,8 +1601,8 @@ Session::set_state (const XMLNode& node, int version)
                        //goto out;
                } else {
                        /* We can't load Bundles yet as they need to be able
-                          to convert from port names to Port objects, which can't happen until
-                          later */
+                        * to convert from port names to Port objects, which can't happen until
+                        * later */
                        _bundle_xml_node = new XMLNode (*child);
                }
        }
@@ -1544,6 +1685,10 @@ Session::set_state (const XMLNode& node, int version)
                }
        }
 
+       if ((child = find_named_node (node, X_("Selection")))) {
+               _selection->set_state (*child, version);
+       }
+
        update_route_record_state ();
 
        /* here beginneth the second phase ... */
@@ -1555,7 +1700,7 @@ Session::set_state (const XMLNode& node, int version)
        state_tree = 0;
        return 0;
 
-  out:
+out:
        delete state_tree;
        state_tree = 0;
        return ret;
@@ -1612,11 +1757,7 @@ Session::XMLRouteFactory (const XMLNode& node, int version)
        XMLNode* ds_child = find_named_node (node, X_("Diskstream"));
 
        DataType type = DataType::AUDIO;
-       XMLProperty const * prop = node.property("default-type");
-
-       if (prop) {
-               type = DataType (prop->value());
-       }
+       node.get_property("default-type", type);
 
        assert (type != DataType::NIL);
 
@@ -1624,31 +1765,31 @@ Session::XMLRouteFactory (const XMLNode& node, int version)
 
                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 (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->init()) {
+                       return ret;
+               }
 
-                if (track->set_state (node, version)) {
-                        return ret;
-                }
+               if (track->set_state (node, version)) {
+                       return ret;
+               }
 
-                BOOST_MARK_TRACK (track);
-                ret = track;
+               BOOST_MARK_TRACK (track);
+               ret = track;
 
        } else {
                PresentationInfo::Flag flags = PresentationInfo::get_flags (node);
                boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML"), flags));
 
-                if (r->init () == 0 && r->set_state (node, version) == 0) {
-                       BOOST_MARK_ROUTE (r);
-                        ret = r;
-                }
+               if (r->init () == 0 && r->set_state (node, version) == 0) {
+                       BOOST_MARK_ROUTE (r);
+                       ret = r;
+               }
        }
 
        return ret;
@@ -1669,11 +1810,7 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version)
        }
 
        DataType type = DataType::AUDIO;
-       XMLProperty const * prop = node.property("default-type");
-
-       if (prop) {
-               type = DataType (prop->value());
-       }
+       node.get_property("default-type", type);
 
        assert (type != DataType::NIL);
 
@@ -1691,33 +1828,33 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version)
 
                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 (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->init()) {
+                       return ret;
+               }
 
-                if (track->set_state (node, version)) {
-                        return ret;
-                }
+               if (track->set_state (node, version)) {
+                       return ret;
+               }
 
                track->set_diskstream (*i);
 
                BOOST_MARK_TRACK (track);
-                ret = track;
+               ret = track;
 
        } else {
                PresentationInfo::Flag flags = PresentationInfo::get_flags (node);
                boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML"), flags));
 
-                if (r->init () == 0 && r->set_state (node, version) == 0) {
-                       BOOST_MARK_ROUTE (r);
-                        ret = r;
-                }
+               if (r->init () == 0 && r->set_state (node, version) == 0) {
+                       BOOST_MARK_ROUTE (r);
+                       ret = r;
+               }
        }
 
        return ret;
@@ -1839,11 +1976,11 @@ Session::XMLRegionFactory (const XMLNode& node, bool full)
                        }
                }
 
-                if (!type || type->value() == "audio") {
-                        return boost::shared_ptr<Region>(XMLAudioRegionFactory (node, full));
-                } else if (type->value() == "midi") {
-                        return boost::shared_ptr<Region>(XMLMidiRegionFactory (node, full));
-                }
+               if (!type || type->value() == "audio") {
+                       return boost::shared_ptr<Region>(XMLAudioRegionFactory (node, full));
+               } else if (type->value() == "midi") {
+                       return boost::shared_ptr<Region>(XMLMidiRegionFactory (node, full));
+               }
 
        } catch (failed_constructor& err) {
                return boost::shared_ptr<Region> ();
@@ -1867,9 +2004,7 @@ Session::XMLAudioRegionFactory (const XMLNode& node, bool /*full*/)
                return boost::shared_ptr<AudioRegion>();
        }
 
-       if ((prop = node.property (X_("channels"))) != 0) {
-               nchans = atoi (prop->value().c_str());
-       }
+       node.get_property (X_("channels"), nchans);
 
        if ((prop = node.property ("name")) == 0) {
                cerr << "no name for this region\n";
@@ -2064,90 +2199,135 @@ Session::load_sources (const XMLNode& node)
 {
        XMLNodeList nlist;
        XMLNodeConstIterator niter;
-       boost::shared_ptr<Source> source; /* don't need this but it stops some
-                                          * versions of gcc complaining about
-                                          * discarded return values.
-                                          */
+       /* don't need this but it stops some
+        * versions of gcc complaining about
+        * discarded return values.
+        */
+       boost::shared_ptr<Source> source;
 
        nlist = node.children();
 
        set_dirty();
+       std::map<std::string, std::string> relocation;
 
        for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
-          retry:
+#ifdef PLATFORM_WINDOWS
+               int old_mode = 0;
+#endif
+
+               XMLNode srcnode (**niter);
+               bool try_replace_abspath = true;
+
+retry:
                try {
-                       if ((source = XMLSourceFactory (**niter)) == 0) {
+#ifdef PLATFORM_WINDOWS
+                       // do not show "insert media" popups (files embedded from removable media).
+                       old_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
+#endif
+                       if ((source = XMLSourceFactory (srcnode)) == 0) {
                                error << _("Session: cannot create Source from XML description.") << endmsg;
                        }
+#ifdef PLATFORM_WINDOWS
+                       SetErrorMode(old_mode);
+#endif
 
                } catch (MissingSource& err) {
+#ifdef PLATFORM_WINDOWS
+                       SetErrorMode(old_mode);
+#endif
+
+                       /* try previous abs path replacements first */
+                       if (try_replace_abspath && Glib::path_is_absolute (err.path)) {
+                               std::string dir = Glib::path_get_dirname (err.path);
+                               std::map<std::string, std::string>::const_iterator rl = relocation.find (dir);
+                               if (rl != relocation.end ()) {
+                                       std::string newpath = Glib::build_filename (rl->second, Glib::path_get_basename (err.path));
+                                       if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
+                                               srcnode.set_property ("origin", newpath);
+                                               try_replace_abspath = false;
+                                               goto retry;
+                                       }
+                               }
+                       }
 
-                        int user_choice;
+                       int user_choice;
+                       _missing_file_replacement = "";
 
                        if (err.type == DataType::MIDI && Glib::path_is_absolute (err.path)) {
-                               error << string_compose (_("A external MIDI file is missing. %1 cannot currently recover from missing external MIDI files"),
-                                                        PROGRAM_NAME) << endmsg;
+                               error << string_compose (_("An external MIDI file is missing. %1 cannot currently recover from missing external MIDI files"),
+                                               PROGRAM_NAME) << endmsg;
                                return -1;
                        }
 
-                        if (!no_questions_about_missing_files) {
+                       if (!no_questions_about_missing_files) {
                                user_choice = MissingFile (this, err.path, err.type).get_value_or (-1);
                        } else {
-                                user_choice = -2;
-                        }
-
-                        switch (user_choice) {
-                        case 0:
-                                /* user added a new search location, so try again */
-                                goto retry;
+                               user_choice = -2;
+                       }
 
+                       switch (user_choice) {
+                               case 0:
+                                       /* user added a new search location
+                                        * or selected a new absolute path,
+                                        * so try again */
+                                       if (Glib::path_is_absolute (err.path)) {
+                                               if (!_missing_file_replacement.empty ()) {
+                                                       /* replace origin, in XML */
+                                                       std::string newpath = Glib::build_filename (
+                                                                       _missing_file_replacement, Glib::path_get_basename (err.path));
+                                                       srcnode.set_property ("origin", newpath);
+                                                       relocation[Glib::path_get_dirname (err.path)] = _missing_file_replacement;
+                                                       _missing_file_replacement = "";
+                                               }
+                                       }
+                                       goto retry;
 
-                        case 1:
-                                /* user asked to quit the entire session load
-                                 */
-                                return -1;
 
-                        case 2:
-                                no_questions_about_missing_files = true;
-                                goto retry;
+                               case 1:
+                                       /* user asked to quit the entire session load */
+                                       return -1;
 
-                        case 3:
-                                no_questions_about_missing_files = true;
-                                /* fallthru */
+                               case 2:
+                                       no_questions_about_missing_files = true;
+                                       goto retry;
 
-                        case -1:
-                        default:
-                               switch (err.type) {
+                               case 3:
+                                       no_questions_about_missing_files = true;
+                                       /* fallthru */
 
-                               case DataType::AUDIO:
-                                       source = SourceFactory::createSilent (*this, **niter, max_framecnt, _current_frame_rate);
-                                       break;
+                               case -1:
+                               default:
+                                       switch (err.type) {
 
-                               case DataType::MIDI:
-                                       /* The MIDI file is actually missing so
-                                        * just create a new one in the same
-                                        * location. Do not announce its
-                                        */
-                                       string fullpath;
+                                               case DataType::AUDIO:
+                                                       source = SourceFactory::createSilent (*this, **niter, max_framecnt, _current_frame_rate);
+                                                       break;
 
-                                       if (!Glib::path_is_absolute (err.path)) {
-                                               fullpath = Glib::build_filename (source_search_path (DataType::MIDI).front(), err.path);
-                                       } else {
-                                               /* this should be an unrecoverable error: we would be creating a MIDI file outside
-                                                  the session tree.
-                                               */
-                                               return -1;
+                                               case DataType::MIDI:
+                                                       /* The MIDI file is actually missing so
+                                                        * just create a new one in the same
+                                                        * location. Do not announce its
+                                                        */
+                                                       string fullpath;
+
+                                                       if (!Glib::path_is_absolute (err.path)) {
+                                                               fullpath = Glib::build_filename (source_search_path (DataType::MIDI).front(), err.path);
+                                                       } else {
+                                                               /* this should be an unrecoverable error: we would be creating a MIDI file outside
+                                                                * the session tree.
+                                                                */
+                                                               return -1;
+                                                       }
+                                                       /* Note that we do not announce the source just yet - we need to reset its ID before we do that */
+                                                       source = SourceFactory::createWritable (DataType::MIDI, *this, fullpath, false, _current_frame_rate, false, false);
+                                                       /* reset ID to match the missing one */
+                                                       source->set_id (**niter);
+                                                       /* Now we can announce it */
+                                                       SourceFactory::SourceCreated (source);
+                                                       break;
                                        }
-                                       /* Note that we do not announce the source just yet - we need to reset its ID before we do that */
-                                       source = SourceFactory::createWritable (DataType::MIDI, *this, fullpath, false, _current_frame_rate, false, false);
-                                       /* reset ID to match the missing one */
-                                       source->set_id (**niter);
-                                       /* Now we can announce it */
-                                       SourceFactory::SourceCreated (source);
                                        break;
-                               }
-                                break;
-                        }
+                       }
                }
        }
 
@@ -2247,7 +2427,7 @@ Session::save_template (string template_name, bool replace_existing)
 void
 Session::refresh_disk_space ()
 {
-#if __APPLE__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
+#if __APPLE__ || __FreeBSD__ || __NetBSD__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
 
        Glib::Threads::Mutex::Lock lm (space_lock);
 
@@ -2257,10 +2437,15 @@ Session::refresh_disk_space ()
        _total_free_4k_blocks_uncertain = false;
 
        for (vector<space_and_path>::iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+#if defined(__NetBSD__)
+               struct statvfs statfsbuf;
 
+               statvfs (i->path.c_str(), &statfsbuf);
+#else
                struct statfs statfsbuf;
-               statfs (i->path.c_str(), &statfsbuf);
 
+               statfs (i->path.c_str(), &statfsbuf);
+#endif
                double const scale = statfsbuf.f_bsize / 4096.0;
 
                /* See if this filesystem is read-only */
@@ -2294,9 +2479,9 @@ Session::refresh_disk_space ()
        vector<string> scanned_volumes;
        vector<string>::iterator j;
        vector<space_and_path>::iterator i;
-    DWORD nSectorsPerCluster, nBytesPerSector,
-          nFreeClusters, nTotalClusters;
-    char disk_drive[4];
+       DWORD nSectorsPerCluster, nBytesPerSector,
+             nFreeClusters, nTotalClusters;
+       char disk_drive[4];
        bool volume_found;
 
        _total_free_4k_blocks = 0;
@@ -2560,6 +2745,25 @@ Session::possible_states () const
        return possible_states(_path);
 }
 
+RouteGroup*
+Session::new_route_group (const std::string& name)
+{
+       RouteGroup* rg = NULL;
+
+       for (std::list<RouteGroup*>::const_iterator i = _route_groups.begin (); i != _route_groups.end (); ++i) {
+               if ((*i)->name () == name) {
+                       rg = *i;
+                       break;
+               }
+       }
+
+       if (!rg) {
+               rg = new RouteGroup (*this, name);
+               add_route_group (rg);
+       }
+       return (rg);
+}
+
 void
 Session::add_route_group (RouteGroup* g)
 {
@@ -2615,7 +2819,7 @@ Session::route_group_by_name (string name)
 RouteGroup&
 Session::all_route_group() const
 {
-        return *_all_route_group;
+       return *_all_route_group;
 }
 
 void
@@ -2745,27 +2949,27 @@ Session::commit_reversible_command (Command *cmd)
 static bool
 accept_all_audio_files (const string& path, void* /*arg*/)
 {
-        if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
-                return false;
-        }
+       if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
+               return false;
+       }
 
-        if (!AudioFileSource::safe_audio_file_extension (path)) {
-                return false;
-        }
+       if (!AudioFileSource::safe_audio_file_extension (path)) {
+               return false;
+       }
 
-        return true;
+       return true;
 }
 
 static bool
 accept_all_midi_files (const string& path, void* /*arg*/)
 {
-        if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
-                return false;
-        }
+       if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
+               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)));
+       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)));
 }
 
 static bool
@@ -2857,14 +3061,17 @@ Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_th
                return 0;
        }
 
-       this_snapshot_path = _path;
-       this_snapshot_path += legalize_for_path (_current_snapshot_name);
+       this_snapshot_path = Glib::build_filename (_path, legalize_for_path (_current_snapshot_name));
        this_snapshot_path += statefile_suffix;
 
        for (vector<string>::iterator i = state_files.begin(); i != state_files.end(); ++i) {
 
+               cerr << "Looking at snapshot " << (*i) << " ( with this = [" << this_snapshot_path << "])\n";
+
                if (exclude_this_snapshot && *i == this_snapshot_path) {
+                       cerr << "\texcluded\n";
                        continue;
+
                }
 
                if (find_all_sources (*i, result) < 0) {
@@ -2876,18 +3083,18 @@ Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_th
 }
 
 struct RegionCounter {
-    typedef std::map<PBD::ID,boost::shared_ptr<AudioSource> > AudioSourceList;
-    AudioSourceList::iterator iter;
-    boost::shared_ptr<Region> region;
-    uint32_t count;
+       typedef std::map<PBD::ID,boost::shared_ptr<AudioSource> > AudioSourceList;
+       AudioSourceList::iterator iter;
+       boost::shared_ptr<Region> region;
+       uint32_t count;
 
-    RegionCounter() : count (0) {}
+       RegionCounter() : count (0) {}
 };
 
 int
 Session::ask_about_playlist_deletion (boost::shared_ptr<Playlist> p)
 {
-        boost::optional<int> r = AskAboutPlaylistDeletion (p);
+       boost::optional<int> r = AskAboutPlaylistDeletion (p);
        return r.get_value_or (1);
 }
 
@@ -2945,7 +3152,7 @@ Session::can_cleanup_peakfiles () const
                warning << _("Cannot cleanup peak-files for read-only session.") << endmsg;
                return false;
        }
-        if (record_status() == Recording) {
+       if (record_status() == Recording) {
                error << _("Cannot cleanup peak-files while recording") << endmsg;
                return false;
        }
@@ -2995,6 +3202,12 @@ Session::cleanup_peakfiles ()
        return 0;
 }
 
+static void
+merge_all_sources (boost::shared_ptr<const Playlist> pl, std::set<boost::shared_ptr<Source> >* all_sources)
+{
+       pl->deep_sources (*all_sources);
+}
+
 int
 Session::cleanup_sources (CleanupReport& rep)
 {
@@ -3005,14 +3218,14 @@ Session::cleanup_sources (CleanupReport& rep)
        string midi_path;
        vector<string> candidates;
        vector<string> unused;
-       set<string> all_sources;
-       bool used;
+       set<string> sources_used_by_all_snapshots;
        string spath;
        int ret = -1;
        string tmppath1;
        string tmppath2;
        Searchpath asp;
        Searchpath msp;
+       set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
 
        _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
 
@@ -3034,10 +3247,9 @@ Session::cleanup_sources (CleanupReport& rep)
                goto out;
        }
 
-        /* sync the "all regions" property of each playlist with its current state
-         */
+       /* sync the "all regions" property of each playlist with its current state */
 
-        playlists->sync_all_regions_with_regions ();
+       playlists->sync_all_regions_with_regions ();
 
        /* find all un-used sources */
 
@@ -3052,10 +3264,10 @@ Session::cleanup_sources (CleanupReport& rep)
                ++tmp;
 
                /* do not bother with files that are zero size, otherwise we remove the current "nascent"
-                  capture files.
-               */
+                * capture files.
+                */
 
-               if (!i->second->used() && (i->second->length(i->second->timeline_position() > 0))) {
+               if (!i->second->used() && (i->second->length(i->second->timeline_position()) > 0)) {
                        dead_sources.push_back (i->second);
                        i->second->drop_references ();
                }
@@ -3083,74 +3295,98 @@ Session::cleanup_sources (CleanupReport& rep)
        find_files_matching_filter (candidates, audio_path, accept_all_audio_files, (void *) 0, true, true);
        find_files_matching_filter (candidates, midi_path, accept_all_midi_files, (void *) 0, true, true);
 
-       /* find all sources, but don't use this snapshot because the
-          state file on disk still references sources we may have already
-          dropped.
-       */
+       /* add sources from all other snapshots as "used", but don't use this
+                snapshot because the state file on disk still references sources we
+                may have already dropped.
+                */
 
-       find_all_sources_across_snapshots (all_sources, true);
+       find_all_sources_across_snapshots (sources_used_by_all_snapshots, true);
 
-       /*  add our current source list
+       /* Although the region factory has a list of all regions ever created
+        * for this session, we're only interested in regions actually in
+        * playlists right now. So merge all playlist regions lists together.
+        *
+        * This will include the playlists used within compound regions.
         */
 
+       playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot));
+
+       /*  add our current source list
+       */
+
        for (SourceMap::iterator i = sources.begin(); i != sources.end(); ) {
                boost::shared_ptr<FileSource> fs;
-                SourceMap::iterator tmp = i;
-                ++tmp;
+               SourceMap::iterator tmp = i;
+               ++tmp;
 
-               if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) != 0) {
+               if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) == 0) {
+                       /* not a file */
+                       i = tmp;
+                       continue;
+               }
 
-                       /* this is mostly for windows which doesn't allow file
-                        * renaming if the file is in use. But we don't special
-                        * case it because we need to know if this causes
-                        * problems, and the easiest way to notice that is to
-                        * keep it in place for all platforms.
-                        */
+               /* this is mostly for windows which doesn't allow file
+                * renaming if the file is in use. But we do not special
+                * case it because we need to know if this causes
+                * problems, and the easiest way to notice that is to
+                * keep it in place for all platforms.
+                */
 
-                       fs->close ();
+               fs->close ();
 
-                       if (!fs->is_stub()) {
+               if (!fs->is_stub()) {
 
-                               if (playlists->source_use_count (fs) != 0) {
-                                       all_sources.insert (fs->path());
-                               } else {
+                       /* Note that we're checking a list of all
+                        * sources across all snapshots with the list
+                        * of sources used by this snapshot.
+                        */
 
-                                       /* 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.
-                                       */
+                       if (sources_used_by_this_snapshot.find (i->second) != sources_used_by_this_snapshot.end()) {
+                               /* this source is in use by this snapshot */
+                               sources_used_by_all_snapshots.insert (fs->path());
+                               cerr << "Source from source list found in used_by_this_snapshot (" << fs->path() << ")\n";
+                       } else {
+                               cerr << "Source from source list NOT found in used_by_this_snapshot (" << fs->path() << ")\n";
+                               /* this source is NOT in use by this snapshot */
 
-                                       RegionFactory::remove_regions_using_source (i->second);
+                               /* remove all related regions from RegionFactory master list */
 
-                                       // also remove source from all_sources
+                               RegionFactory::remove_regions_using_source (i->second);
 
-                                       for (set<string>::iterator j = all_sources.begin(); j != all_sources.end(); ++j) {
-                                               spath = Glib::path_get_basename (*j);
-                                               if (spath == i->second->name()) {
-                                                       all_sources.erase (j);
-                                                       break;
-                                               }
-                                       }
+                               /* remove from our current source list
+                                * also. We may not remove it from
+                                * disk, because it may be used by
+                                * other snapshots, but it isn't used inside this
+                                * snapshot anymore, so we don't need a
+                                * reference to it.
+                                */
 
-                                       sources.erase (i);
-                               }
+                               sources.erase (i);
                        }
                }
 
-                i = tmp;
+               i = tmp;
        }
 
+       /* now check each candidate source to see if it exists in the list of
+        * sources_used_by_all_snapshots. If it doesn't, put it into "unused".
+        */
+
+       cerr << "Candidates: " << candidates.size() << endl;
+       cerr << "Used by others: " << sources_used_by_all_snapshots.size() << endl;
+
        for (vector<string>::iterator x = candidates.begin(); x != candidates.end(); ++x) {
 
-               used = false;
+               bool used = false;
                spath = *x;
 
-               for (set<string>::iterator i = all_sources.begin(); i != all_sources.end(); ++i) {
+               for (set<string>::iterator i = sources_used_by_all_snapshots.begin(); i != sources_used_by_all_snapshots.end(); ++i) {
 
                        tmppath1 = canonical_path (spath);
                        tmppath2 = canonical_path ((*i));
 
+                       cerr << "\t => " << tmppath2 << endl;
+
                        if (tmppath1 == tmppath2) {
                                used = true;
                                break;
@@ -3162,6 +3398,14 @@ Session::cleanup_sources (CleanupReport& rep)
                }
        }
 
+       cerr << "Actually unused: " << unused.size() << endl;
+
+       if (unused.empty()) {
+               /* Nothing to do */
+               ret = 0;
+               goto out;
+       }
+
        /* now try to move all unused files into the "dead" directory(ies) */
 
        for (vector<string>::iterator x = unused.begin(); x != unused.end(); ++x) {
@@ -3170,9 +3414,9 @@ Session::cleanup_sources (CleanupReport& rep)
                string newpath;
 
                /* don't move the file across filesystems, just
-                  stick it in the `dead_dir_name' directory
-                  on whichever filesystem it was already on.
-               */
+                * stick it in the `dead_dir_name' directory
+                * on whichever filesystem it was already on.
+                */
 
                if ((*x).find ("/sounds/") != string::npos) {
 
@@ -3218,44 +3462,35 @@ Session::cleanup_sources (CleanupReport& rep)
 
                        if (version == 999) {
                                error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
-                                                 newpath)
-                                     << endmsg;
+                                               newpath)
+                                       << endmsg;
                        } else {
                                newpath = newpath_v;
                        }
 
-               } else {
-
-                       /* it doesn't exist, or we can't read it or something */
-
                }
 
-               g_stat ((*x).c_str(), &statbuf);
-
-               if (::rename ((*x).c_str(), newpath.c_str()) != 0) {
-                       error << string_compose (_("cannot rename unused file source from %1 to %2 (%3)"),
-                                         (*x), newpath, strerror (errno))
-                             << endmsg;
-                       goto out;
+               if ((g_stat ((*x).c_str(), &statbuf) != 0) || (::g_rename ((*x).c_str(), newpath.c_str()) != 0)) {
+                       error << string_compose (_("cannot rename unused file source from %1 to %2 (%3)"), (*x),
+                                       newpath, g_strerror (errno)) << endmsg;
+                       continue;
                }
 
-               /* see if there an easy to find peakfile for this file, and remove it.
-                */
+               /* see if there an easy to find peakfile for this file, and remove it.  */
 
-                string base = Glib::path_get_basename (*x);
-                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 base = Glib::path_get_basename (*x);
+               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 = construct_peak_filepath (base);
 
-               if (Glib::file_test (peakpath.c_str(), Glib::FILE_TEST_EXISTS)) {
-                       if (::g_unlink (peakpath.c_str()) != 0) {
-                               error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
-                                                         peakpath, _path, strerror (errno))
-                                     << endmsg;
+               if (Glib::file_test (peakpath.c_str (), Glib::FILE_TEST_EXISTS)) {
+                       if (::g_unlink (peakpath.c_str ()) != 0) {
+                               error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"), peakpath, _path,
+                                               g_strerror (errno)) << endmsg;
                                /* try to back out */
-                               ::rename (newpath.c_str(), _path.c_str());
+                               ::g_rename (newpath.c_str (), _path.c_str ());
                                goto out;
                        }
                }
@@ -3269,13 +3504,13 @@ Session::cleanup_sources (CleanupReport& rep)
        _history.clear ();
 
        /* save state so we don't end up a session file
-          referring to non-existent sources.
-       */
+        * referring to non-existent sources.
+        */
 
        save_state ("");
        ret = 0;
 
-  out:
+out:
        _state_of_the_state = (StateOfTheState) (_state_of_the_state & ~InCleanup);
 
        return ret;
@@ -3296,7 +3531,7 @@ Session::cleanup_trash_sources (CleanupReport& rep)
 
                dead_dir = Glib::build_filename ((*i).path, dead_dir_name);
 
-                clear_directory (dead_dir, &rep.space, &rep.paths);
+               clear_directory (dead_dir, &rep.space, &rep.paths);
        }
 
        return 0;
@@ -3305,23 +3540,20 @@ Session::cleanup_trash_sources (CleanupReport& rep)
 void
 Session::set_dirty ()
 {
-       /* never mark session dirty during loading */
+       /* return early if there's nothing to do */
+       if (dirty ()) {
+               return;
+       }
 
+       /* never mark session dirty during loading */
        if (_state_of_the_state & Loading) {
                return;
        }
 
-       bool was_dirty = dirty();
-
        _state_of_the_state = StateOfTheState (_state_of_the_state | Dirty);
-
-
-       if (!was_dirty) {
-               DirtyChanged(); /* EMIT SIGNAL */
-       }
+       DirtyChanged(); /* EMIT SIGNAL */
 }
 
-
 void
 Session::set_clean ()
 {
@@ -3329,7 +3561,6 @@ Session::set_clean ()
 
        _state_of_the_state = Clean;
 
-
        if (was_dirty) {
                DirtyChanged(); /* EMIT SIGNAL */
        }
@@ -3392,6 +3623,12 @@ Session::controllable_by_id (const PBD::ID& id)
        return boost::shared_ptr<Controllable>();
 }
 
+boost::shared_ptr<AutomationControl>
+Session::automation_control_by_id (const PBD::ID& id)
+{
+       return boost::dynamic_pointer_cast<AutomationControl> (controllable_by_id (id));
+}
+
 boost::shared_ptr<Controllable>
 Session::controllable_by_descriptor (const ControllableDescriptor& desc)
 {
@@ -3403,13 +3640,17 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
        case ControllableDescriptor::NamedRoute:
        {
                std::string str = desc.top_level_name();
+
                if (str == "Master" || str == "master") {
                        s = _master_out;
-               } else if (str == "control" || str == "listen") {
+               } else if (str == "control" || str == "listen" || str == "monitor" || str == "Monitor") {
                        s = _monitor_out;
+               } else if (str == "auditioner") {
+                       s = auditioner;
                } else {
                        s = route_by_name (desc.top_level_name());
                }
+
                break;
        }
 
@@ -3417,6 +3658,14 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                s = get_remote_nth_stripable (desc.presentation_order(), PresentationInfo::Route);
                break;
 
+       case ControllableDescriptor::PresentationOrderTrack:
+               s = get_remote_nth_stripable (desc.presentation_order(), PresentationInfo::Track);
+               break;
+
+       case ControllableDescriptor::PresentationOrderBus:
+               s = get_remote_nth_stripable (desc.presentation_order(), PresentationInfo::Bus);
+               break;
+
        case ControllableDescriptor::PresentationOrderVCA:
                s = get_remote_nth_stripable (desc.presentation_order(), PresentationInfo::VCA);
                break;
@@ -3442,7 +3691,7 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                break;
 
        case ControllableDescriptor::Solo:
-                c = s->solo_control();
+               c = s->solo_control();
                break;
 
        case ControllableDescriptor::Mute:
@@ -3450,7 +3699,7 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                break;
 
        case ControllableDescriptor::Recenable:
-               c = s->recenable_control ();
+               c = s->rec_enable_control ();
                break;
 
        case ControllableDescriptor::PanDirection:
@@ -3532,6 +3781,11 @@ Session::add_instant_xml (XMLNode& node, bool write_to_config)
 XMLNode*
 Session::instant_xml (const string& node_name)
 {
+#ifdef MIXBUS // "Safe Mode" (shift + click open) -> also ignore instant.xml
+       if (get_disable_all_loaded_plugins ()) {
+               return NULL;
+       }
+#endif
        return Stateful::instant_xml (node_name, _path);
 }
 
@@ -3615,17 +3869,25 @@ Session::restore_history (string snapshot_name)
        // replace history
        _history.clear();
 
-       for (XMLNodeConstIterator it  = tree.root()->children().begin(); it != tree.root()->children().end(); it++) {
+       for (XMLNodeConstIterator it  = tree.root()->children().begin(); it != tree.root()->children().end(); ++it) {
 
                XMLNode *t = *it;
                UndoTransaction* ut = new UndoTransaction ();
-               struct timeval tv;
 
-               ut->set_name(t->property("name")->value());
-               stringstream ss(t->property("tv-sec")->value());
-               ss >> tv.tv_sec;
-               ss.str(t->property("tv-usec")->value());
-               ss >> tv.tv_usec;
+               std::string name;
+               int64_t tv_sec;
+               int64_t tv_usec;
+
+               if (!t->get_property ("name", name) || !t->get_property ("tv-sec", tv_sec) ||
+                   !t->get_property ("tv-usec", tv_usec)) {
+                       continue;
+               }
+
+               ut->set_name (name);
+
+               struct timeval tv;
+               tv.tv_sec = tv_sec;
+               tv.tv_usec = tv_usec;
                ut->set_timestamp(tv);
 
                for (XMLNodeConstIterator child_it  = t->children().begin();
@@ -3702,11 +3964,13 @@ Session::config_changed (std::string p, bool ours)
 
        } else if (p == "auto-loop") {
 
+       } else if (p == "session-monitoring") {
+
        } else if (p == "auto-input") {
 
                if (Config->get_monitoring_model() == HardwareMonitoring && transport_rolling()) {
                        /* auto-input only makes a difference if we're rolling */
-                        set_track_monitor_input_status (!config.get_auto_input());
+                       set_track_monitor_input_status (!config.get_auto_input());
                }
 
        } else if (p == "punch-in") {
@@ -3805,6 +4069,10 @@ Session::config_changed (std::string p, bool ours)
                        _clicking = false;
                }
 
+       } else if (p == "click-record-only") {
+
+                       _click_rec_only = Config->get_click_record_only();
+
        } else if (p == "click-gain") {
 
                if (_click_gain) {
@@ -3822,10 +4090,6 @@ Session::config_changed (std::string p, bool ours)
 
                _mmc->enable_send (Config->get_send_mmc ());
 
-       } else if (p == "midi-feedback") {
-
-               session_midi_feedback = Config->get_midi_feedback();
-
        } else if (p == "jack-time-master") {
 
                engine().reset_timebase ();
@@ -3904,30 +4168,30 @@ Session::set_history_depth (uint32_t d)
 int
 Session::load_diskstreams_2X (XMLNode const & node, int)
 {
-        XMLNodeList          clist;
-        XMLNodeConstIterator citer;
+       XMLNodeList          clist;
+       XMLNodeConstIterator citer;
 
-        clist = node.children();
+       clist = node.children();
 
-        for (citer = clist.begin(); citer != clist.end(); ++citer) {
+       for (citer = clist.begin(); citer != clist.end(); ++citer) {
 
-                try {
-                        /* diskstreams added automatically by DiskstreamCreated handler */
-                        if ((*citer)->name() == "AudioDiskstream" || (*citer)->name() == "DiskStream") {
+               try {
+                       /* diskstreams added automatically by DiskstreamCreated handler */
+                       if ((*citer)->name() == "AudioDiskstream" || (*citer)->name() == "DiskStream") {
                                boost::shared_ptr<AudioDiskstream> dsp (new AudioDiskstream (*this, **citer));
                                _diskstreams_2X.push_back (dsp);
-                        } else {
-                                error << _("Session: unknown diskstream type in XML") << endmsg;
-                        }
-                }
-
-                catch (failed_constructor& err) {
-                        error << _("Session: could not load diskstream via XML state") << endmsg;
-                        return -1;
-                }
-        }
-
-        return 0;
+                       } else {
+                               error << _("Session: unknown diskstream type in XML") << endmsg;
+                       }
+               }
+
+               catch (failed_constructor& err) {
+                       error << _("Session: could not load diskstream via XML state") << endmsg;
+                       return -1;
+               }
+       }
+
+       return 0;
 }
 
 /** Connect things to the MMC object */
@@ -3974,16 +4238,15 @@ Session::setup_midi_machine_control ()
 boost::shared_ptr<Controllable>
 Session::solo_cut_control() const
 {
-        /* the solo cut control is a bit of an anomaly, at least as of Febrary 2011. There are no other
-           controls in Ardour that currently get presented to the user in the GUI that require
-           access as a Controllable and are also NOT owned by some SessionObject (e.g. Route, or MonitorProcessor).
-
-           its actually an RCConfiguration parameter, so we use a ProxyControllable to wrap
-           it up as a Controllable. Changes to the Controllable will just map back to the RCConfiguration
-           parameter.
-        */
-
-        return _solo_cut_control;
+       /* the solo cut control is a bit of an anomaly, at least as of Febrary 2011. There are no other
+        * controls in Ardour that currently get presented to the user in the GUI that require
+        * access as a Controllable and are also NOT owned by some SessionObject (e.g. Route, or MonitorProcessor).
+        *
+        * its actually an RCConfiguration parameter, so we use a ProxyControllable to wrap
+        * it up as a Controllable. Changes to the Controllable will just map back to the RCConfiguration
+        * parameter.
+        */
+       return _solo_cut_control;
 }
 
 void
@@ -3996,7 +4259,7 @@ Session::save_snapshot_name (const std::string & n)
        instant_xml ("LastUsedSnapshot");
 
        XMLNode* last_used_snapshot = new XMLNode ("LastUsedSnapshot");
-       last_used_snapshot->add_property ("name", string(n));
+       last_used_snapshot->set_property ("name", n);
        add_instant_xml (*last_used_snapshot, false);
 }
 
@@ -4022,7 +4285,7 @@ Session::rename (const std::string& new_name)
                error << _("Cannot rename read-only session.") << endmsg;
                return 0; // don't show "messed up" warning
        }
-        if (record_status() == Recording) {
+       if (record_status() == Recording) {
                error << _("Cannot rename session while recording") << endmsg;
                return 0; // don't show "messed up" warning
        }
@@ -4152,12 +4415,12 @@ Session::rename (const std::string& new_name)
 
                if (::g_rename (old_interchange_dir.c_str(), new_interchange_dir.c_str()) != 0) {
                        cerr << string_compose (_("renaming %s as %2 failed (%3)"),
-                                                old_interchange_dir, new_interchange_dir,
-                                                g_strerror (errno))
-                             << endl;
+                                               old_interchange_dir, new_interchange_dir,
+                                               g_strerror (errno))
+                            << endl;
                        error << string_compose (_("renaming %s as %2 failed (%3)"),
-                                                old_interchange_dir, new_interchange_dir,
-                                                g_strerror (errno))
+                                                old_interchange_dir, new_interchange_dir,
+                                                g_strerror (errno))
                              << endmsg;
                        return 1;
                }
@@ -4225,68 +4488,82 @@ Session::rename (const std::string& new_name)
 }
 
 int
-Session::get_session_info_from_path (XMLTree& tree, const string& xmlpath)
+Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFormat& data_format, std::string& program_version)
 {
+       bool found_sr = false;
+       bool found_data_format = false;
+       program_version = "";
+
        if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
                return -1;
-        }
+       }
 
-       if (!tree.read (xmlpath)) {
+       xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
+       if (ctxt == NULL) {
                return -1;
        }
+       xmlDocPtr doc = xmlCtxtReadFile (ctxt, xmlpath.c_str(), NULL, XML_PARSE_HUGE);
 
-       return 0;
-}
-
-int
-Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFormat& data_format)
-{
-       XMLTree tree;
-       bool found_sr = false;
-       bool found_data_format = false;
-
-       if (get_session_info_from_path (tree, xmlpath)) {
+       if (doc == NULL) {
+               xmlFreeParserCtxt(ctxt);
                return -1;
        }
 
-       /* sample rate */
-
-       XMLProperty const * prop;
-       XMLNode const * root (tree.root());
+       xmlNodePtr node = xmlDocGetRootElement(doc);
 
-       if ((prop = root->property (X_("sample-rate"))) != 0) {
-               sample_rate = atoi (prop->value());
-               found_sr = true;
+       if (node == NULL) {
+               xmlFreeParserCtxt(ctxt);
+               xmlFreeDoc (doc);
+               return -1;
        }
 
-       const XMLNodeList& children (root->children());
-       for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
-               const XMLNode* child = *c;
-               if (child->name() == "Config") {
-                       const XMLNodeList& options (child->children());
-                       for (XMLNodeList::const_iterator oc = options.begin(); oc != options.end(); ++oc) {
-                               XMLNode const * option = *oc;
-                               XMLProperty const * name = option->property("name");
-
-                               if (!name) {
-                                       continue;
-                               }
+       /* sample rate */
 
-                               if (name->value() == "native-file-data-format") {
-                                       XMLProperty const * value = option->property ("value");
-                                       if (value) {
-                                               SampleFormat fmt = (SampleFormat) string_2_enum (option->property ("value")->value(), fmt);
-                                               data_format = fmt;
-                                               found_data_format = true;
-                                               break;
-                                       }
-                               }
-                       }
-               }
-               if (found_data_format) {
-                       break;
-               }
-       }
+       xmlAttrPtr attr;
+       for (attr = node->properties; attr; attr = attr->next) {
+               if (!strcmp ((const char*)attr->name, "sample-rate") && attr->children) {
+                       sample_rate = atoi ((char*)attr->children->content);
+                       found_sr = true;
+               }
+       }
+
+       node = node->children;
+       while (node != NULL) {
+                if (!strcmp((const char*) node->name, "ProgramVersion")) {
+                        xmlChar* val = xmlGetProp (node, (const xmlChar*)"modified-with");
+                        if (val) {
+                                program_version = string ((const char*)val);
+                                size_t sep = program_version.find_first_of("-");
+                                if (sep != string::npos) {
+                                        program_version = program_version.substr (0, sep);
+                                }
+                        }
+                        xmlFree (val);
+                }
+                if (strcmp((const char*) node->name, "Config")) {
+                        node = node->next;
+                        continue;
+                }
+                for (node = node->children; node; node = node->next) {
+                        xmlChar* pv = xmlGetProp (node, (const xmlChar*)"name");
+                        if (pv && !strcmp ((const char*)pv, "native-file-data-format")) {
+                                xmlFree (pv);
+                                xmlChar* val = xmlGetProp (node, (const xmlChar*)"value");
+                                if (val) {
+                                        SampleFormat fmt = (SampleFormat) string_2_enum (string ((const char*)val), fmt);
+                                        data_format = fmt;
+                                        found_data_format = true;
+                                }
+                                xmlFree (val);
+                                break;
+                        }
+                        xmlFree (pv);
+                }
+                break;
+       }
+
+       xmlFreeParserCtxt(ctxt);
+       xmlFreeDoc (doc);
 
        return !(found_sr && found_data_format); // zero if they are both found
 }
@@ -4727,7 +5004,6 @@ Session::save_as (SaveAs& saveas)
                        }
                }
 
-
                _path = to_dir;
                set_snapshot_name (saveas.new_name);
                _name = saveas.new_name;
@@ -4753,7 +5029,6 @@ Session::save_as (SaveAs& saveas)
 
                bool was_dirty = dirty ();
 
-               save_state ("", false, false, !saveas.include_media);
                save_default_options ();
 
                if (saveas.copy_media && saveas.copy_external) {
@@ -4768,6 +5043,10 @@ Session::save_as (SaveAs& saveas)
 
                if (!saveas.switch_to) {
 
+                       /* save the new state */
+
+                       save_state ("", false, false, !saveas.include_media);
+
                        /* switch back to the way things were */
 
                        _path = old_path;
@@ -4797,10 +5076,25 @@ Session::save_as (SaveAs& saveas)
                        session_dirs.push_back (sp);
                        refresh_disk_space ();
 
+                       _writable = exists_and_writable (_path);
+
                        /* ensure that all existing tracks reset their current capture source paths
                         */
                        reset_write_sources (true, true);
 
+                       /* creating new write sources marks the session as
+                          dirty. If the new session is empty, then
+                          save_state() thinks we're saving a template and will
+                          not mark the session as clean. So do that here,
+                          before we save state.
+                       */
+
+                       if (!saveas.include_media) {
+                               _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
+                       }
+
+                       save_state ("", false, false, !saveas.include_media);
+
                        /* the copying above was based on actually discovering files, not just iterating over the sources list.
                           But if we're going to switch to the new (copied) session, we need to change the paths in the sources also.
                        */
@@ -4846,3 +5140,340 @@ Session::save_as (SaveAs& saveas)
 
        return 0;
 }
+
+static void set_progress (Progress* p, size_t n, size_t t)
+{
+       p->set_progress (float (n) / float(t));
+}
+
+int
+Session::archive_session (const std::string& dest,
+                          const std::string& name,
+                          ArchiveEncode compress_audio,
+                          bool only_used_sources,
+                          Progress* progress)
+{
+       if (dest.empty () || name.empty ()) {
+               return -1;
+       }
+
+       /* save current values */
+       bool was_dirty = dirty ();
+       string old_path = _path;
+       string old_name = _name;
+       string old_snapshot = _current_snapshot_name;
+       string old_sd = _session_dir->root_path();
+       string old_config_search_path[DataType::num_types];
+       old_config_search_path[DataType::AUDIO] = config.get_audio_search_path ();
+       old_config_search_path[DataType::MIDI]  = config.get_midi_search_path ();
+
+       /* ensure that session-path is included in search-path */
+       bool ok = false;
+       for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+               if ((*sd).path == old_path) {
+                       ok = true;
+               }
+       }
+       if (!ok) {
+               return -1;
+       }
+
+       /* create temporary dir to save session to */
+#ifdef PLATFORM_WINDOWS
+       char tmp[256] = "C:\\TEMP\\";
+       GetTempPath (sizeof (tmp), tmp);
+#else
+       char const* tmp = getenv("TMPDIR");
+       if (!tmp) {
+               tmp = "/tmp/";
+       }
+#endif
+       if ((strlen (tmp) + 21) > 1024) {
+               return -1;
+       }
+
+       char tmptpl[1024];
+       strcpy (tmptpl, tmp);
+       strcat (tmptpl, "ardourarchive-XXXXXX");
+       char*  tmpdir = g_mkdtemp (tmptpl);
+
+       if (!tmpdir) {
+               return -1;
+       }
+
+       std::string to_dir = std::string (tmpdir);
+
+       /* switch session directory temporarily */
+       (*_session_dir) = to_dir;
+
+       if (!_session_dir->create()) {
+               (*_session_dir) = old_sd;
+               remove_directory (to_dir);
+               return -1;
+       }
+
+       /* prepare archive */
+       string archive = Glib::build_filename (dest, name + ".tar.xz");
+
+       PBD::ScopedConnectionList progress_connection;
+       PBD::FileArchive ar (archive);
+       if (progress) {
+               ar.progress.connect_same_thread (progress_connection, boost::bind (&set_progress, progress, _1, _2));
+       }
+
+       /* collect files to archive */
+       std::map<string,string> filemap;
+
+       vector<string> do_not_copy_extensions;
+       do_not_copy_extensions.push_back (statefile_suffix);
+       do_not_copy_extensions.push_back (pending_suffix);
+       do_not_copy_extensions.push_back (backup_suffix);
+       do_not_copy_extensions.push_back (temp_suffix);
+       do_not_copy_extensions.push_back (history_suffix);
+
+       vector<string> blacklist_dirs;
+       blacklist_dirs.push_back (string (peak_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (analysis_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (dead_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (export_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (externals_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (plugins_dir_name) + G_DIR_SEPARATOR);
+
+       std::map<boost::shared_ptr<AudioFileSource>, std::string> orig_sources;
+       std::map<boost::shared_ptr<AudioFileSource>, float> orig_gain;
+
+       set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
+       if (only_used_sources) {
+               playlists->sync_all_regions_with_regions ();
+               playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot), false);
+       }
+
+       // collect audio sources for this session, calc total size for encoding
+       // add option to only include *used* sources (see Session::cleanup_sources)
+       size_t total_size = 0;
+       {
+               Glib::Threads::Mutex::Lock lm (source_lock);
+               for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+                       boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (i->second);
+                       if (!afs || afs->readable_length () == 0) {
+                               continue;
+                       }
+
+                       if (only_used_sources) {
+                               if (!afs->used()) {
+                                       continue;
+                               }
+                               if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
+                                       continue;
+                               }
+                       }
+
+                       std::string from = afs->path();
+
+                       if (compress_audio != NO_ENCODE) {
+                               total_size += afs->readable_length ();
+                       } else {
+                               if (afs->within_session()) {
+                                       filemap[from] = make_new_media_path (from, name, name);
+                               } else {
+                                       filemap[from] = make_new_media_path (from, name, name);
+                                       remove_dir_from_search_path (Glib::path_get_dirname (from), DataType::AUDIO);
+                               }
+                       }
+               }
+       }
+
+       /* encode audio */
+       if (compress_audio != NO_ENCODE) {
+               if (progress) {
+                       progress->set_progress (2); // set to "encoding"
+                       progress->set_progress (0);
+               }
+
+               Glib::Threads::Mutex::Lock lm (source_lock);
+               for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+                       boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (i->second);
+                       if (!afs || afs->readable_length () == 0) {
+                               continue;
+                       }
+
+                       if (only_used_sources) {
+                               if (!afs->used()) {
+                                       continue;
+                               }
+                               if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
+                                       continue;
+                               }
+                       }
+
+                       orig_sources[afs] = afs->path();
+                       orig_gain[afs]    = afs->gain();
+
+                       std::string new_path = make_new_media_path (afs->path (), to_dir, name);
+                       new_path = Glib::build_filename (Glib::path_get_dirname (new_path), PBD::basename_nosuffix (new_path) + ".flac");
+                       g_mkdir_with_parents (Glib::path_get_dirname (new_path).c_str (), 0755);
+
+                       if (progress) {
+                               progress->descend ((float)afs->readable_length () / total_size);
+                       }
+
+                       try {
+                               SndFileSource* ns = new SndFileSource (*this, *(afs.get()), new_path, compress_audio == FLAC_16BIT, progress);
+                               afs->replace_file (new_path);
+                               afs->set_gain (ns->gain(), true);
+                               delete ns;
+                       } catch (...) {
+                               cerr << "failed to encode " << afs->path() << " to " << new_path << "\n";
+                       }
+
+                       if (progress) {
+                               progress->ascend ();
+                       }
+               }
+       }
+
+       if (progress) {
+               progress->set_progress (-1); // set to "archiving"
+               progress->set_progress (0);
+       }
+
+       /* index files relevant for this session */
+       for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+               vector<string> files;
+
+               size_t prefix_len = (*sd).path.size();
+               if (prefix_len > 0 && (*sd).path.at (prefix_len - 1) != G_DIR_SEPARATOR) {
+                       ++prefix_len;
+               }
+
+               find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
+
+               static const std::string audiofile_dir_string = string (sound_dir_name) + G_DIR_SEPARATOR;
+               static const std::string videofile_dir_string = string (video_dir_name) + G_DIR_SEPARATOR;
+               static const std::string midifile_dir_string  = string (midi_dir_name)  + G_DIR_SEPARATOR;
+
+               for (vector<string>::const_iterator i = files.begin (); i != files.end (); ++i) {
+                       std::string from = *i;
+
+#ifdef __APPLE__
+                       string filename = Glib::path_get_basename (from);
+                       std::transform (filename.begin(), filename.end(), filename.begin(), ::toupper);
+                       if (filename == ".DS_STORE") {
+                               continue;
+                       }
+#endif
+
+                       if (from.find (audiofile_dir_string) != string::npos) {
+                               ; // handled above
+                       } else if (from.find (midifile_dir_string) != string::npos) {
+                               filemap[from] = make_new_media_path (from, name, name);
+                       } else if (from.find (videofile_dir_string) != string::npos) {
+                               filemap[from] = make_new_media_path (from, name, name);
+                       } else {
+                               bool do_copy = true;
+                               for (vector<string>::iterator v = blacklist_dirs.begin(); v != blacklist_dirs.end(); ++v) {
+                                       if (from.find (*v) != string::npos) {
+                                               do_copy = false;
+                                               break;
+                                       }
+                               }
+                               for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
+                                       if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
+                                               do_copy = false;
+                                               break;
+                                       }
+                               }
+
+                               if (do_copy) {
+                                       filemap[from] = name + G_DIR_SEPARATOR + from.substr (prefix_len);
+                               }
+                       }
+               }
+       }
+
+       /* write session file */
+       _path = to_dir;
+       g_mkdir_with_parents (externals_dir ().c_str (), 0755);
+#ifdef LV2_SUPPORT
+       PBD::Unwinder<bool> uw (LV2Plugin::force_state_save, true);
+#endif
+       save_state (name);
+       save_default_options ();
+
+       size_t prefix_len = _path.size();
+       if (prefix_len > 0 && _path.at (prefix_len - 1) != G_DIR_SEPARATOR) {
+               ++prefix_len;
+       }
+
+       /* collect session-state files */
+       vector<string> files;
+       do_not_copy_extensions.clear ();
+       do_not_copy_extensions.push_back (history_suffix);
+
+       blacklist_dirs.clear ();
+       blacklist_dirs.push_back (string (externals_dir_name) + G_DIR_SEPARATOR);
+
+       find_files_matching_filter (files, to_dir, accept_all_files, 0, false, true, true);
+       for (vector<string>::const_iterator i = files.begin (); i != files.end (); ++i) {
+               std::string from = *i;
+               bool do_copy = true;
+               for (vector<string>::iterator v = blacklist_dirs.begin(); v != blacklist_dirs.end(); ++v) {
+                       if (from.find (*v) != string::npos) {
+                               do_copy = false;
+                               break;
+                       }
+               }
+               for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
+                       if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
+                               do_copy = false;
+                               break;
+                       }
+               }
+               if (do_copy) {
+                       filemap[from] = name + G_DIR_SEPARATOR + from.substr (prefix_len);
+               }
+       }
+
+       /* restore original values */
+       _path = old_path;
+       _name = old_name;
+       set_snapshot_name (old_snapshot);
+       (*_session_dir) = old_sd;
+       if (was_dirty) {
+               set_dirty ();
+       }
+       config.set_audio_search_path (old_config_search_path[DataType::AUDIO]);
+       config.set_midi_search_path (old_config_search_path[DataType::MIDI]);
+
+       for (std::map<boost::shared_ptr<AudioFileSource>, std::string>::iterator i = orig_sources.begin (); i != orig_sources.end (); ++i) {
+               i->first->replace_file (i->second);
+       }
+       for (std::map<boost::shared_ptr<AudioFileSource>, float>::iterator i = orig_gain.begin (); i != orig_gain.end (); ++i) {
+               i->first->set_gain (i->second, true);
+       }
+
+       int rv = ar.create (filemap);
+       remove_directory (to_dir);
+
+       return rv;
+}
+
+void
+Session::undo (uint32_t n)
+{
+       if (actively_recording()) {
+               return;
+       }
+
+       _history.undo (n);
+}
+
+void
+Session::redo (uint32_t n)
+{
+       if (actively_recording()) {
+               return;
+       }
+
+       _history.redo (n);
+}