Add option to limit automatable control parmaters
[ardour.git] / libs / ardour / session_state.cc
index 5729c889bd8380bfc88cd506284b03eadaa87a79..34a6a4348061b84773c07536a5afd271daceb298 100644 (file)
@@ -50,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/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"
 
 #include "ardour/amp.h"
 #include "ardour/async_midi_port.h"
-#include "ardour/audio_diskstream.h"
 #include "ardour/audio_track.h"
 #include "ardour/audioengine.h"
 #include "ardour/audiofilesource.h"
 #include "ardour/automation_control.h"
 #include "ardour/boost_debug.h"
 #include "ardour/butler.h"
-#include "ardour/controllable_descriptor.h"
 #include "ardour/control_protocol_manager.h"
 #include "ardour/directory_names.h"
+#include "ardour/disk_reader.h"
 #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/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/transport_master_manager.h"
+#include "ardour/types_convert.h"
 #include "ardour/user_bundle.h"
 #include "ardour/vca.h"
 #include "ardour/vca_manager.h"
@@ -175,7 +183,6 @@ Session::pre_engine_init (string fullpath)
        g_atomic_int_set (&_capture_load, 100);
        set_next_event ();
        _all_route_group->set_active (true, this);
-       interpolation.add_channel_to (0, 0);
 
        if (config.get_use_video_sync()) {
                waiting_for_sync_offset = true;
@@ -187,14 +194,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 */
 
@@ -216,7 +223,7 @@ Session::post_engine_init ()
        BootMessage (_("Set block size and sample rate"));
 
        set_block_size (_engine.samples_per_cycle());
-       set_frame_rate (_engine.sample_rate());
+       set_sample_rate (_engine.sample_rate());
 
        BootMessage (_("Using configuration"));
 
@@ -228,16 +235,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<samplecnt_t(void)> timer_func (boost::bind (&Session::audible_sample, 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,10 +260,18 @@ Session::post_engine_init ()
                /* tempo map requires sample rate knowledge */
 
                delete _tempo_map;
-               _tempo_map = new TempoMap (_current_frame_rate);
+               _tempo_map = new TempoMap (_current_sample_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,10 +280,11 @@ Session::post_engine_init ()
 
                /* crossfades require sample rate knowledge */
 
-               SndFileSource::setup_standard_crossfades (*this, frame_rate());
+               SndFileSource::setup_standard_crossfades (*this, sample_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();
+               DiskReader::allocate_working_buffers();
                refresh_disk_space ();
 
                /* we're finally ready to call set_state() ... all objects have
@@ -274,8 +292,14 @@ Session::post_engine_init ()
                 */
 
                if (state_tree) {
-                       if (set_state (*state_tree->root(), Stateful::loading_state_version)) {
-                               return -1;
+                       try {
+                               if (set_state (*state_tree->root(), Stateful::loading_state_version)) {
+                                       error << _("Could not set session state from XML") << endmsg;
+                                       return -4;
+                               }
+                       } catch (PBD::unknown_enumeration& e) {
+                               error << _("Session state: ") << e.what() << endmsg;
+                               return -4;
                        }
                } else {
                        // set_state() will call setup_raid_path(), but if it's a new session we need
@@ -290,7 +314,7 @@ Session::post_engine_init ()
 
                Config->map_parameters (ff);
                config.map_parameters (ft);
-                _butler->map_parameters ();
+               _butler->map_parameters ();
 
                /* Reset all panners */
 
@@ -338,11 +362,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"));
@@ -379,8 +406,8 @@ Session::post_engine_init ()
        boost::shared_ptr<RouteList> rl = routes.reader();
        for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
                boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<Track> (*r);
-               if (trk && !trk->hidden()) {
-                       trk->seek (_transport_frame, true);
+               if (trk && !trk->is_private_route()) {
+                       trk->seek (_transport_sample, true);
                }
        }
 
@@ -407,7 +434,7 @@ Session::session_loaded ()
        /* Now, finally, we can fill the playback buffers */
 
        BootMessage (_("Filling playback buffers"));
-       force_locate (_transport_frame, false);
+       force_locate (_transport_sample, false);
 }
 
 string
@@ -615,72 +642,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;
@@ -689,9 +680,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
@@ -759,11 +750,13 @@ Session::remove_state (string snapshot_name)
                error << string_compose(_("Could not remove session file at path \"%1\" (%2)"),
                                xml_path, g_strerror (errno)) << endmsg;
        }
+
+       StateSaved (snapshot_name); /* EMIT SIGNAL */
 }
 
 /** @param snapshot_name Name to save under, without .ardour / .pending prefix */
 int
-Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot, bool template_only)
+Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot, bool template_only, bool for_archive, bool only_used_assets)
 {
        DEBUG_TRACE (DEBUG::Locale, string_compose ("Session::save_state locale '%1'\n", setlocale (LC_NUMERIC, NULL)));
 
@@ -773,6 +766,10 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
        /* prevent concurrent saves from different threads */
 
        Glib::Threads::Mutex::Lock lm (save_state_lock);
+       Glib::Threads::Mutex::Lock lx (save_source_lock, Glib::Threads::NOT_LOCK);
+       if (!for_archive) {
+               lx.acquire ();
+       }
 
        if (!_writable || (_state_of_the_state & CannotSave)) {
                return 1;
@@ -784,11 +781,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 && !for_archive) {
+               /* snapshot, close midi */
+               fork_state = switch_to_snapshot ? SwitchToSnapshot : SnapshotKeep;
        }
 
 #ifndef NDEBUG
@@ -805,10 +801,11 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
                }
        }
 
+       PBD::Unwinder<bool> uw (LV2Plugin::force_state_save, for_archive);
+
        SessionSaveUnderway (); /* EMIT SIGNAL */
 
        bool mark_as_clean = true;
-
        if (!snapshot_name.empty() && !switch_to_snapshot) {
                mark_as_clean = false;
        }
@@ -817,7 +814,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 (false, fork_state, only_used_assets));
        }
 
        if (snapshot_name.empty()) {
@@ -850,7 +847,9 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
        std::string tmp_path(_session_dir->root_path());
        tmp_path = Glib::build_filename (tmp_path, legalize_for_path (snapshot_name) + temp_suffix);
 
+#ifndef NDEBUG
        cerr << "actually writing state to " << tmp_path << endl;
+#endif
 
        if (!tree.write (tmp_path)) {
                error << string_compose (_("state could not be saved to %1"), tmp_path) << endmsg;
@@ -862,7 +861,9 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
 
        } else {
 
+#ifndef NDEBUG
                cerr << "renaming state to " << xml_path << endl;
+#endif
 
                if (::g_rename (tmp_path.c_str(), xml_path.c_str()) != 0) {
                        error << string_compose (_("could not rename temporary session file %1 to %2 (%3)"),
@@ -875,7 +876,33 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
                }
        }
 
-       if (!pending) {
+       //Mixbus auto-backup mechanism
+       if(Profile->get_mixbus()) {
+               if (pending) {  //"pending" save means it's a backup, or some other non-user-initiated save;  a good time to make a backup
+                       // make a serialized safety backup
+                       // (will make one periodically but only one per hour is left on disk)
+                       // these backup files go into a separated folder
+                       char timebuf[128];
+                       time_t n;
+                       struct tm local_time;
+                       time (&n);
+                       localtime_r (&n, &local_time);
+                       strftime (timebuf, sizeof(timebuf), "%y-%m-%d.%H", &local_time);
+                       std::string save_path(session_directory().backup_path());
+                       save_path += G_DIR_SEPARATOR;
+                       save_path += legalize_for_path(_current_snapshot_name);
+                       save_path += "-";
+                       save_path += timebuf;
+                       save_path += statefile_suffix;
+                       if ( !tree.write (save_path) )
+                                       error << string_compose(_("Could not save backup file at path \"%1\" (%2)"),
+                                                       save_path, g_strerror (errno)) << endmsg;
+               }
+
+               StateSaved (snapshot_name); /* EMIT SIGNAL */
+       }
+
+       if (!pending && !for_archive) {
 
                save_history (snapshot_name);
 
@@ -902,8 +929,14 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
 int
 Session::restore_state (string snapshot_name)
 {
-       if (load_state (snapshot_name) == 0) {
-               set_state (*state_tree->root(), Stateful::loading_state_version);
+       try {
+               if (load_state (snapshot_name) == 0) {
+                       set_state (*state_tree->root(), Stateful::loading_state_version);
+               }
+       } catch (...) {
+               // SessionException
+               // unknown_enumeration
+               return -1;
        }
 
        return 0;
@@ -926,7 +959,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;
                }
@@ -939,10 +972,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;
 
@@ -966,22 +999,13 @@ Session::load_state (string snapshot_name)
                return -1;
        }
 
-       XMLProperty const * prop;
+       std::string version;
+       root.get_property ("version", version);
+       Stateful::loading_state_version = parse_stateful_loading_version (version);
 
-       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) {
-                       /* old school version format */
-                       if (prop->value()[0] == '2') {
-                               Stateful::loading_state_version = 2000;
-                       } else {
-                               Stateful::loading_state_version = 3000;
-                       }
-               } else {
-                       Stateful::loading_state_version = atoi (prop->value());
-               }
+       if ((Stateful::loading_state_version / 1000L) > (CURRENT_SESSION_FILE_VERSION / 1000L)) {
+               cerr << "Session-version: " << Stateful::loading_state_version << " is not supported. Current: " << CURRENT_SESSION_FILE_VERSION << "\n";
+               throw SessionException (string_compose (_("Incomatible Session Version. That session was created with a newer version of %1"), PROGRAM_NAME));
        }
 
        if (Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION && _writable) {
@@ -1000,20 +1024,6 @@ Session::load_state (string snapshot_name)
                                return -1;
                        }
                }
-       } else {
-               XMLNode* child;
-               if ((child = find_named_node (root, "ProgramVersion")) != 0) {
-                       if ((prop = child->property ("modified-with")) != 0) {
-                               std::string modified_with = prop->value ();
-
-                               const double modified_with_version = atof (modified_with.substr ( modified_with.find(" ", 0) + 1, string::npos).c_str());
-                               const int modified_with_revision = atoi (modified_with.substr (modified_with.find("-", 0) + 1, string::npos).c_str());
-
-                               if (modified_with_version <= 5.3 && !(modified_with_version == 5.3 && modified_with_revision > 42)) {
-                                       _midi_regions_use_bbt_beats = true;
-                               }
-                       }
-               }
        }
 
        save_snapshot_name (snapshot_name);
@@ -1024,7 +1034,6 @@ Session::load_state (string snapshot_name)
 int
 Session::load_options (const XMLNode& node)
 {
-       LocaleGuard lg;
        config.set_variables (node);
        return 0;
 }
@@ -1036,13 +1045,15 @@ Session::save_default_options ()
 }
 
 XMLNode&
-Session::get_state()
+Session::get_state ()
 {
-       return state(true);
+       /* this is not directly called, but required by PBD::Stateful */
+       assert (0);
+       return state (false, NormalSave);
 }
 
 XMLNode&
-Session::get_template()
+Session::get_template ()
 {
        /* if we don't disable rec-enable, diskstreams
           will believe they need to store their capture
@@ -1051,33 +1062,123 @@ Session::get_template()
 
        disable_record (false);
 
-       return state(false);
+       return state (true, NormalSave);
 }
 
+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());
+}
+
+static void
+merge_all_sources (boost::shared_ptr<const Playlist> pl, std::set<boost::shared_ptr<Source> >* all_sources)
+{
+       pl->deep_sources (*all_sources);
+}
+
+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 save_template, snapshot_t snapshot_type, bool only_used_assets)
 {
        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);
+       PBD::Unwinder<bool> uw (Automatable::skip_saving_automation, save_template);
+
+       node->set_property("version", CURRENT_SESSION_FILE_VERSION);
 
        child = node->add_child ("ProgramVersion");
-       child->add_property("created-with", created_with);
+       child->set_property("created-with", created_with);
 
        std::string modified_with = string_compose ("%1 %2", PROGRAM_NAME, revision);
-       child->add_property("modified-with", modified_with);
+       child->set_property("modified-with", modified_with);
 
        /* store configuration settings */
 
-       if (full_state) {
+       if (!save_template) {
 
-               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_sample_rate);
 
                if (session_dirs.size() > 1) {
 
@@ -1107,27 +1208,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);
        }
 
-       node->add_property ("end-is-free", _session_range_end_is_free ? X_("yes") : X_("no"));
-
        /* 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 */
 
@@ -1140,53 +1236,142 @@ 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 (save_template) {
+               /* 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());
 
        child = node->add_child ("Sources");
 
-       if (full_state) {
+       if (!save_template) {
                Glib::Threads::Mutex::Lock sl (source_lock);
 
+               set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
+
+               if (only_used_assets) {
+                       playlists->sync_all_regions_with_regions ();
+                       playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot), false);
+               }
+
                for (SourceMap::iterator siter = sources.begin(); siter != sources.end(); ++siter) {
 
                        /* 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 (only_used_assets) {
+                               /* skip only unused audio files */
+                               boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (fs);
+                               if (afs && !afs->used()) {
+                                       continue;
+                               }
+                               if (afs && sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
+                                       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, Temporal::Beats(), std::numeric_limits<Temporal::Beats>::max())) {
+                                               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());
                }
        }
 
        child = node->add_child ("Regions");
 
-       if (full_state) {
+       if (!save_template) {
                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) {
-                               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 ());
+
+               if (!only_used_assets) {
+                       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());
 
@@ -1194,30 +1379,39 @@ 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];
+                               if (i->first->playlist () == 0 && only_used_assets) {
+                                       continue;
+                               }
                                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);
+                               /* see above, child is still "Regions" here  */
+                               if (i->second->playlist() == 0 && only_used_assets) {
+                                       if (boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>( i->second)) {
+                                               child->add_child_nocopy (ar->get_basic_state ());
+                                       } else {
+                                               child->add_child_nocopy (ar->get_state ());
+                                       }
+                               }
                        }
                }
        }
 
+       if (!save_template) {
 
-
-       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);
-               range->set (max_framepos, 0);
+               Location* range = new Location (*this, 0, 0, _("session"), Location::IsSessionRange, 0);
+               range->set (max_samplepos, 0);
                loc.add (range);
                XMLNode& locations_state = loc.get_state();
 
@@ -1230,6 +1424,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");
@@ -1249,28 +1452,22 @@ Session::state (bool full_state)
        {
                boost::shared_ptr<RouteList> r = routes.reader ();
 
-               RoutePublicOrderSorter cmp;
-               RouteList public_order (*r);
-               public_order.sort (cmp);
-
-               /* the sort should have put the monitor out first */
+               route_id_compare cmp;
+               RouteList xml_node_order (*r);
+               xml_node_order.sort (cmp);
 
-               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());
-                               } else {
+                               if (save_template) {
                                        child->add_child_nocopy ((*i)->get_template());
+                               } else {
+                                       child->add_child_nocopy ((*i)->get_state());
                                }
                        }
                }
        }
 
-       playlists->add_state (node, full_state);
+       playlists->add_state (node, save_template, !only_used_assets);
 
        child = node->add_child ("RouteGroups");
        for (list<RouteGroup *>::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) {
@@ -1279,21 +1476,16 @@ Session::state (bool full_state)
 
        if (_click_io) {
                XMLNode* gain_child = node->add_child ("Click");
-               gain_child->add_child_nocopy (_click_io->state (full_state));
-               gain_child->add_child_nocopy (_click_gain->state (full_state));
-       }
-
-       if (_ltc_input) {
-               XMLNode* ltc_input_child = node->add_child ("LTC-In");
-               ltc_input_child->add_child_nocopy (_ltc_input->state (full_state));
+               gain_child->add_child_nocopy (_click_io->get_state ());
+               gain_child->add_child_nocopy (_click_gain->get_state ());
        }
 
-       if (_ltc_input) {
+       if (_ltc_output) {
                XMLNode* ltc_output_child = node->add_child ("LTC-Out");
-               ltc_output_child->add_child_nocopy (_ltc_output->state (full_state));
+               ltc_output_child->add_child_nocopy (_ltc_output->get_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());
 
@@ -1316,7 +1508,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);
        }
@@ -1327,8 +1519,7 @@ Session::state (bool full_state)
 XMLNode&
 Session::get_control_protocol_state ()
 {
-       ControlProtocolManager& cpm (ControlProtocolManager::instance());
-       return cpm.get_state();
+       return ControlProtocolManager::instance().get_state ();
 }
 
 int
@@ -1337,7 +1528,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);
@@ -1347,18 +1537,15 @@ 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_sample_rate)) {
 
-               _base_frame_rate = atoi (prop->value());
-               _nominal_frame_rate = _base_frame_rate;
+               _nominal_sample_rate = _base_sample_rate;
 
                assert (AudioEngine::instance()->running ());
-               if (_base_frame_rate != AudioEngine::instance()->sample_rate ()) {
-                       boost::optional<int> r = AskAboutSampleRateMismatch (_base_frame_rate, _current_frame_rate);
+               if (_base_sample_rate != AudioEngine::instance()->sample_rate ()) {
+                       boost::optional<int> r = AskAboutSampleRateMismatch (_base_sample_rate, _current_sample_rate);
                        if (r.get_value_or (0)) {
                                goto out;
                        }
@@ -1367,43 +1554,36 @@ Session::set_state (const XMLNode& node, int version)
 
        created_with = "unknown";
        if ((child = find_named_node (node, "ProgramVersion")) != 0) {
-               if ((prop = child->property (X_("created-with"))) != 0) {
-                       created_with = prop->value ();
-               }
+               child->get_property (X_("created-with"), created_with);
        }
 
        setup_raid_path(_session_dir->root_path());
 
-       if ((prop = node.property (X_("end-is-free"))) != 0) {
-               _session_range_end_is_free = string_is_affirmative (prop->value());
-       }
+       node.get_property (X_("end-is-free"), _session_range_end_is_free);
 
-       if ((prop = node.property (X_("id-counter"))) != 0) {
-               uint64_t x;
-               sscanf (prop->value().c_str(), "%" PRIu64, &x);
-               ID::init_counter (x);
+       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);
        }
@@ -1432,9 +1612,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;
@@ -1495,21 +1675,12 @@ 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);
                }
        }
 
-       if (version < 3000) {
-               if ((child = find_named_node (node, X_("DiskStreams"))) == 0) {
-                       error << _("Session: XML state has no diskstreams section") << endmsg;
-                       goto out;
-               } else if (load_diskstreams_2X (*child, version)) {
-                       goto out;
-               }
-       }
-
        if ((child = find_named_node (node, VCAManager::xml_node_name)) != 0) {
                _vca_manager->set_state (*child, version);
        }
@@ -1525,9 +1696,6 @@ Session::set_state (const XMLNode& node, int version)
 
        Slavable::Assign (_vca_manager); /* EMIT SIGNAL */
 
-       /* our diskstreams list is no longer needed as they are now all owned by their Route */
-       _diskstreams_2X.clear ();
-
        if (version >= 3000) {
 
                if ((child = find_named_node (node, "RouteGroups")) == 0) {
@@ -1561,7 +1729,7 @@ Session::set_state (const XMLNode& node, int version)
        }
 
        if ((child = find_named_node (node, ControlProtocolManager::state_node_name)) != 0) {
-               ControlProtocolManager::instance().set_state (*child, version);
+               ControlProtocolManager::instance().set_state (*child, 1 /* here: session-specific state */);
        }
 
        if ((child = find_named_node (node, "Script"))) {
@@ -1574,11 +1742,15 @@ Session::set_state (const XMLNode& node, int version)
                                (*_lua_load)(std::string ((const char*)buf, size));
                        } catch (luabridge::LuaException const& e) {
                                cerr << "LuaException:" << e.what () << endl;
-                       }
+                       } catch (...) { }
                        g_free (buf);
                }
        }
 
+       if ((child = find_named_node (node, X_("Selection")))) {
+               _selection->set_state (*child, version);
+       }
+
        update_route_record_state ();
 
        /* here beginneth the second phase ... */
@@ -1590,7 +1762,7 @@ Session::set_state (const XMLNode& node, int version)
        state_tree = 0;
        return 0;
 
-  out:
+out:
        delete state_tree;
        state_tree = 0;
        return ret;
@@ -1610,8 +1782,11 @@ Session::load_routes (const XMLNode& node, int version)
        for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
 
                boost::shared_ptr<Route> route;
+
                if (version < 3000) {
                        route = XMLRouteFactory_2X (**niter, version);
+               } else if (version < 5000) {
+                       route = XMLRouteFactory_3X (**niter, version);
                } else {
                        route = XMLRouteFactory (**niter, version);
                }
@@ -1644,46 +1819,99 @@ Session::XMLRouteFactory (const XMLNode& node, int version)
                return ret;
        }
 
-       XMLNode* ds_child = find_named_node (node, X_("Diskstream"));
+       XMLProperty const * pl_prop = node.property (X_("audio-playlist"));
+
+       if (!pl_prop) {
+               pl_prop = node.property (X_("midi-playlist"));
+       }
 
        DataType type = DataType::AUDIO;
-       XMLProperty const * prop = node.property("default-type");
+       node.get_property("default-type", type);
+
+       assert (type != DataType::NIL);
+
+       if (pl_prop) {
+
+               /* has at least 1 playlist, therefore a track ... */
 
-       if (prop) {
-               type = DataType (prop->value());
+               boost::shared_ptr<Track> track;
+
+               if (type == DataType::AUDIO) {
+                       track.reset (new AudioTrack (*this, X_("toBeResetFroXML")));
+               } else {
+                       track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
+               }
+
+               if (track->init()) {
+                       return ret;
+               }
+
+               if (track->set_state (node, version)) {
+                       return ret;
+               }
+
+               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;
+               }
+       }
+
+       return ret;
+}
+
+boost::shared_ptr<Route>
+Session::XMLRouteFactory_3X (const XMLNode& node, int version)
+{
+       boost::shared_ptr<Route> ret;
+
+       if (node.name() != "Route") {
+               return ret;
        }
 
+       XMLNode* ds_child = find_named_node (node, X_("Diskstream"));
+
+       DataType type = DataType::AUDIO;
+       node.get_property("default-type", type);
+
        assert (type != DataType::NIL);
 
        if (ds_child) {
 
                boost::shared_ptr<Track> track;
 
-                if (type == DataType::AUDIO) {
-                        track.reset (new AudioTrack (*this, X_("toBeResetFroXML")));
-                } else {
-                        track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
-                }
+               if (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;
@@ -1704,55 +1932,46 @@ 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);
 
        if (ds_prop) {
 
-               list<boost::shared_ptr<Diskstream> >::iterator i = _diskstreams_2X.begin ();
-               while (i != _diskstreams_2X.end() && (*i)->id() != ds_prop->value()) {
-                       ++i;
-               }
+               /* see comment in current ::set_state() regarding diskstream
+                * state and DiskReader/DiskWRiter.
+                */
 
-               if (i == _diskstreams_2X.end()) {
-                       error << _("Could not find diskstream for route") << endmsg;
-                       return boost::shared_ptr<Route> ();
-               }
+               error << _("Could not find diskstream for route") << endmsg;
+               return boost::shared_ptr<Route> ();
 
                boost::shared_ptr<Track> track;
 
-                if (type == DataType::AUDIO) {
-                        track.reset (new AudioTrack (*this, X_("toBeResetFroXML")));
-                } else {
-                        track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
-                }
-
-                if (track->init()) {
-                        return ret;
-                }
+               if (type == DataType::AUDIO) {
+                       track.reset (new AudioTrack (*this, X_("toBeResetFroXML")));
+               } else {
+                       track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
+               }
 
-                if (track->set_state (node, version)) {
-                        return ret;
-                }
+               if (track->init()) {
+                       return ret;
+               }
 
-               track->set_diskstream (*i);
+               if (track->set_state (node, version)) {
+                       return ret;
+               }
 
                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;
@@ -1874,11 +2093,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> ();
@@ -1902,9 +2121,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";
@@ -2099,90 +2316,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_samplecnt, _current_sample_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_sample_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;
-                        }
+                       }
                }
        }
 
@@ -2208,7 +2470,7 @@ Session::XMLSourceFactory (const XMLNode& node)
 }
 
 int
-Session::save_template (string template_name, bool replace_existing)
+Session::save_template (const string& template_name, const string& description, bool replace_existing)
 {
        if ((_state_of_the_state & CannotSave) || template_name.empty ()) {
                return -1;
@@ -2263,12 +2525,24 @@ Session::save_template (string template_name, bool replace_existing)
        SessionSaveUnderway (); /* EMIT SIGNAL */
 
        XMLTree tree;
-
+       XMLNode* root;
        {
                PBD::Unwinder<std::string> uw (_template_state_dir, template_dir_path);
-               tree.set_root (&get_template());
+               root = &get_template ();
+       }
+
+       root->remove_nodes_and_delete (X_("description"));
+
+       if (!description.empty()) {
+               XMLNode* desc = new XMLNode (X_("description"));
+               XMLNode* desc_cont = new XMLNode (X_("content"), description);
+               desc->add_child_nocopy (*desc_cont);
+
+               root->add_child_nocopy (*desc);
        }
 
+       tree.set_root (root);
+
        if (!tree.write (template_file_path)) {
                error << _("template not saved") << endmsg;
                return -1;
@@ -2282,7 +2556,7 @@ Session::save_template (string template_name, bool replace_existing)
 void
 Session::refresh_disk_space ()
 {
-#if __APPLE__ || __FreeBSD__ || (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);
 
@@ -2292,10 +2566,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 */
@@ -2329,9 +2608,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;
@@ -2595,6 +2874,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)
 {
@@ -2650,7 +2948,7 @@ Session::route_group_by_name (string name)
 RouteGroup&
 Session::all_route_group() const
 {
-        return *_all_route_group;
+       return *_all_route_group;
 }
 
 void
@@ -2780,27 +3078,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
@@ -2914,18 +3212,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);
 }
 
@@ -2983,7 +3281,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;
        }
@@ -3033,12 +3331,6 @@ 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)
 {
@@ -3078,10 +3370,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 */
 
@@ -3096,10 +3387,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 ();
                }
@@ -3128,9 +3419,9 @@ Session::cleanup_sources (CleanupReport& rep)
        find_files_matching_filter (candidates, midi_path, accept_all_midi_files, (void *) 0, true, true);
 
        /* 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.
-       */
+                snapshot because the state file on disk still references sources we
+                may have already dropped.
+                */
 
        find_all_sources_across_snapshots (sources_used_by_all_snapshots, true);
 
@@ -3144,12 +3435,12 @@ Session::cleanup_sources (CleanupReport& rep)
        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) {
                        /* not a file */
@@ -3179,11 +3470,9 @@ Session::cleanup_sources (CleanupReport& rep)
                                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
-                                */
+                               /* this source is NOT in use by this snapshot */
 
-                               /* remove all related regions from RegionFactory master list
-                                */
+                               /* remove all related regions from RegionFactory master list */
 
                                RegionFactory::remove_regions_using_source (i->second);
 
@@ -3199,12 +3488,12 @@ Session::cleanup_sources (CleanupReport& rep)
                        }
                }
 
-                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".
-       */
+        * 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;
@@ -3248,9 +3537,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) {
 
@@ -3296,45 +3585,41 @@ 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;
                        }
 
                }
 
-               if (0 == g_stat ((*x).c_str(), &statbuf)) {
+               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;
+               }
 
-                       if (::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 peakpath = construct_peak_filepath (base);
 
-                                       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, g_strerror (errno))
-                                                 << endmsg;
-                                       /* try to back out */
-                                       ::g_rename (newpath.c_str(), _path.c_str());
-                                       goto out;
-                               }
+               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 */
+                               ::g_rename (newpath.c_str (), _path.c_str ());
+                               goto out;
                        }
-
-                       rep.paths.push_back (*x);
-                       rep.space += statbuf.st_size;
                }
+
+               rep.paths.push_back (*x);
+               rep.space += statbuf.st_size;
        }
 
        /* dump the history list */
@@ -3342,13 +3627,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;
@@ -3369,7 +3654,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;
@@ -3378,23 +3663,20 @@ Session::cleanup_trash_sources (CleanupReport& rep)
 void
 Session::set_dirty ()
 {
-       /* never mark session dirty during loading */
-
-       if (_state_of_the_state & Loading) {
+       /* return early if there's nothing to do */
+       if (dirty ()) {
                return;
        }
 
-       bool was_dirty = dirty();
+       /* never mark session dirty during loading */
+       if (_state_of_the_state & (Loading | Deletion)) {
+               return;
+       }
 
        _state_of_the_state = StateOfTheState (_state_of_the_state | Dirty);
-
-
-       if (!was_dirty) {
-               DirtyChanged(); /* EMIT SIGNAL */
-       }
+       DirtyChanged(); /* EMIT SIGNAL */
 }
 
-
 void
 Session::set_clean ()
 {
@@ -3402,7 +3684,6 @@ Session::set_clean ()
 
        _state_of_the_state = Clean;
 
-
        if (was_dirty) {
                DirtyChanged(); /* EMIT SIGNAL */
        }
@@ -3465,141 +3746,10 @@ Session::controllable_by_id (const PBD::ID& id)
        return boost::shared_ptr<Controllable>();
 }
 
-boost::shared_ptr<Controllable>
-Session::controllable_by_descriptor (const ControllableDescriptor& desc)
+boost::shared_ptr<AutomationControl>
+Session::automation_control_by_id (const PBD::ID& id)
 {
-       boost::shared_ptr<Controllable> c;
-       boost::shared_ptr<Stripable> s;
-       boost::shared_ptr<Route> r;
-
-       switch (desc.top_level_type()) {
-       case ControllableDescriptor::NamedRoute:
-       {
-               std::string str = desc.top_level_name();
-
-               if (str == "Master" || str == "master") {
-                       s = _master_out;
-               } 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;
-       }
-
-       case ControllableDescriptor::PresentationOrderRoute:
-               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;
-
-       case ControllableDescriptor::SelectionCount:
-               s = route_by_selected_count (desc.selection_id());
-               break;
-       }
-
-       if (!s) {
-               return c;
-       }
-
-       r = boost::dynamic_pointer_cast<Route> (s);
-
-       switch (desc.subtype()) {
-       case ControllableDescriptor::Gain:
-               c = s->gain_control ();
-               break;
-
-       case ControllableDescriptor::Trim:
-               c = s->trim_control ();
-               break;
-
-       case ControllableDescriptor::Solo:
-                c = s->solo_control();
-               break;
-
-       case ControllableDescriptor::Mute:
-               c = s->mute_control();
-               break;
-
-       case ControllableDescriptor::Recenable:
-               c = s->rec_enable_control ();
-               break;
-
-       case ControllableDescriptor::PanDirection:
-               c = s->pan_azimuth_control();
-               break;
-
-       case ControllableDescriptor::PanWidth:
-               c = s->pan_width_control();
-               break;
-
-       case ControllableDescriptor::PanElevation:
-               c = s->pan_elevation_control();
-               break;
-
-       case ControllableDescriptor::Balance:
-               /* XXX simple pan control */
-               break;
-
-       case ControllableDescriptor::PluginParameter:
-       {
-               uint32_t plugin = desc.target (0);
-               uint32_t parameter_index = desc.target (1);
-
-               /* revert to zero based counting */
-
-               if (plugin > 0) {
-                       --plugin;
-               }
-
-               if (parameter_index > 0) {
-                       --parameter_index;
-               }
-
-               if (!r) {
-                       return c;
-               }
-
-               boost::shared_ptr<Processor> p = r->nth_plugin (plugin);
-
-               if (p) {
-                       c = boost::dynamic_pointer_cast<ARDOUR::AutomationControl>(
-                               p->control(Evoral::Parameter(PluginAutomation, 0, parameter_index)));
-               }
-               break;
-       }
-
-       case ControllableDescriptor::SendGain: {
-               uint32_t send = desc.target (0);
-               if (send > 0) {
-                       --send;
-               }
-               if (!r) {
-                       return c;
-               }
-               c = r->send_level_controllable (send);
-               break;
-       }
-
-       default:
-               /* relax and return a null pointer */
-               break;
-       }
-
-       return c;
+       return boost::dynamic_pointer_cast<AutomationControl> (controllable_by_id (id));
 }
 
 void
@@ -3617,6 +3767,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);
 }
 
@@ -3700,17 +3855,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;
+
+               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;
+               }
+
                UndoTransaction* ut = new UndoTransaction ();
-               struct timeval tv;
+               ut->set_name (name);
 
-               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;
+               struct timeval tv;
+               tv.tv_sec = tv_sec;
+               tv.tv_usec = tv_usec;
                ut->set_timestamp(tv);
 
                for (XMLNodeConstIterator child_it  = t->children().begin();
@@ -3781,17 +3944,15 @@ Session::config_changed (std::string p, bool ours)
                set_dirty ();
        }
 
-       if (p == "seamless-loop") {
-
-       } else if (p == "rf-speed") {
+       if (p == "auto-loop") {
 
-       } 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") {
@@ -3801,9 +3962,9 @@ Session::config_changed (std::string p, bool ours)
                if ((location = _locations->auto_punch_location()) != 0) {
 
                        if (config.get_punch_in ()) {
-                               replace_event (SessionEvent::PunchIn, location->start());
+                               auto_punch_start_changed (location);
                        } else {
-                               remove_event (location->start(), SessionEvent::PunchIn);
+                               clear_events (SessionEvent::PunchIn);
                        }
                }
 
@@ -3814,7 +3975,7 @@ Session::config_changed (std::string p, bool ours)
                if ((location = _locations->auto_punch_location()) != 0) {
 
                        if (config.get_punch_out()) {
-                               replace_event (SessionEvent::PunchOut, location->end());
+                               auto_punch_end_changed (location);
                        } else {
                                clear_events (SessionEvent::PunchOut);
                        }
@@ -3890,6 +4051,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) {
@@ -3906,10 +4071,11 @@ Session::config_changed (std::string p, bool ours)
        } else if (p == "send-mmc") {
 
                _mmc->enable_send (Config->get_send_mmc ());
-
-       } else if (p == "midi-feedback") {
-
-               session_midi_feedback = Config->get_midi_feedback();
+               if (Config->get_send_mmc ()) {
+                       /* re-initialize MMC */
+                       send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
+                       send_immediate_mmc (MIDI::MachineControlCommand (Timecode::Time ()));
+               }
 
        } else if (p == "jack-time-master") {
 
@@ -3932,11 +4098,7 @@ Session::config_changed (std::string p, bool ours)
                first_file_data_format_reset = false;
 
        } else if (p == "external-sync") {
-               if (!config.get_external_sync()) {
-                       drop_sync_source ();
-               } else {
-                       switch_to_sync_source (Config->get_sync_source());
-               }
+               request_sync_source (TransportMasterManager::instance().current());
        }  else if (p == "denormal-model") {
                setup_fpu ();
        } else if (p == "history-depth") {
@@ -3966,9 +4128,7 @@ Session::config_changed (std::string p, bool ours)
        } else if (p == "timecode-offset" || p == "timecode-offset-negative") {
                last_timecode_valid = false;
        } else if (p == "playback-buffer-seconds") {
-               AudioSource::allocate_working_buffers (frame_rate());
-       } else if (p == "ltc-source-port") {
-               reconnect_ltc_input ();
+               AudioSource::allocate_working_buffers (sample_rate());
        } else if (p == "ltc-sink-port") {
                reconnect_ltc_output ();
        } else if (p == "timecode-generator-offset") {
@@ -3986,35 +4146,6 @@ Session::set_history_depth (uint32_t d)
        _history.set_depth (d);
 }
 
-int
-Session::load_diskstreams_2X (XMLNode const & node, int)
-{
-        XMLNodeList          clist;
-        XMLNodeConstIterator citer;
-
-        clist = node.children();
-
-        for (citer = clist.begin(); citer != clist.end(); ++citer) {
-
-                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;
-}
-
 /** Connect things to the MMC object */
 void
 Session::setup_midi_machine_control ()
@@ -4059,16 +4190,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
@@ -4081,7 +4211,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);
 }
 
@@ -4107,7 +4237,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
        }
@@ -4237,12 +4367,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;
                }
@@ -4310,70 +4440,114 @@ Session::rename (const std::string& new_name)
 }
 
 int
-Session::get_session_info_from_path (XMLTree& tree, const string& xmlpath)
+Session::parse_stateful_loading_version (const std::string& version)
 {
-       if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
-               return -1;
-        }
-
-       if (!tree.read (xmlpath)) {
-               return -1;
+       if (version.empty ()) {
+               /* no version implies very old version of Ardour */
+               return 1000;
        }
 
-       return 0;
+       if (version.find ('.') != string::npos) {
+               /* old school version format */
+               if (version[0] == '2') {
+                       return 2000;
+               } else {
+                       return 3000;
+               }
+       } else {
+               return string_to<int32_t>(version);
+       }
 }
 
 int
-Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFormat& data_format)
+Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFormat& data_format, std::string& program_version)
 {
-       XMLTree tree;
        bool found_sr = false;
        bool found_data_format = false;
+       std::string version;
+       program_version = "";
+
+       if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
+               return -1;
+       }
 
-       if (get_session_info_from_path (tree, xmlpath)) {
+       xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
+       if (ctxt == NULL) {
                return -1;
        }
+       xmlDocPtr doc = xmlCtxtReadFile (ctxt, xmlpath.c_str(), NULL, XML_PARSE_HUGE);
 
-       /* sample rate */
+       if (doc == NULL) {
+               xmlFreeParserCtxt(ctxt);
+               return -1;
+       }
 
-       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 & version*/
 
-                               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;
-                                       }
-                               }
-                       }
+       xmlAttrPtr attr;
+       for (attr = node->properties; attr; attr = attr->next) {
+               if (!strcmp ((const char*)attr->name, "version") && attr->children) {
+                       version = std::string ((char*)attr->children->content);
                }
-               if (found_data_format) {
-                       break;
+               if (!strcmp ((const char*)attr->name, "sample-rate") && attr->children) {
+                       sample_rate = atoi ((char*)attr->children->content);
+                       found_sr = true;
                }
        }
 
-       return !(found_sr && found_data_format); // zero if they are both found
+       if ((parse_stateful_loading_version(version) / 1000L) > (CURRENT_SESSION_FILE_VERSION / 1000L)) {
+               return -1;
+       }
+
+       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) {
+                                        try {
+                                                SampleFormat fmt = (SampleFormat) string_2_enum (string ((const char*)val), fmt);
+                                                data_format = fmt;
+                                                found_data_format = true;
+                                        } catch (PBD::unknown_enumeration& e) {}
+                                }
+                                xmlFree (val);
+                                break;
+                        }
+                        xmlFree (pv);
+                }
+                break;
+       }
+
+       xmlFreeParserCtxt(ctxt);
+       xmlFreeDoc (doc);
+
+       return (found_sr && found_data_format) ? 0 : 1;
 }
 
 std::string
@@ -4506,21 +4680,33 @@ Session::save_as_bring_callback (uint32_t,uint32_t,string)
 }
 
 static string
-make_new_media_path (string old_path, string new_session_folder, string new_session_path)
+make_new_media_path (string old_path, string new_session_folder, string new_session_name)
 {
-       /* typedir is the "midifiles" or "audiofiles" etc. part of the path. */
-
+       // old_path must be in within_session ()
        string typedir = Glib::path_get_basename (Glib::path_get_dirname (old_path));
        vector<string> v;
        v.push_back (new_session_folder); /* full path */
        v.push_back (interchange_dir_name);
-       v.push_back (new_session_path);   /* just one directory/folder */
+       v.push_back (new_session_name);   /* just one directory/folder */
        v.push_back (typedir);
        v.push_back (Glib::path_get_basename (old_path));
 
        return Glib::build_filename (v);
 }
 
+static string
+make_new_audio_path (string filename, string new_session_folder, string new_session_name)
+{
+       vector<string> v;
+       v.push_back (new_session_folder); /* full path */
+       v.push_back (interchange_dir_name);
+       v.push_back (new_session_name);
+       v.push_back (ARDOUR::sound_dir_name);
+       v.push_back (filename);
+
+       return Glib::build_filename (v);
+}
+
 int
 Session::save_as (SaveAs& saveas)
 {
@@ -4812,7 +4998,6 @@ Session::save_as (SaveAs& saveas)
                        }
                }
 
-
                _path = to_dir;
                set_snapshot_name (saveas.new_name);
                _name = saveas.new_name;
@@ -4838,7 +5023,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) {
@@ -4853,6 +5037,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;
@@ -4882,10 +5070,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.
                        */
@@ -4932,6 +5135,386 @@ 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,
+                          FileArchive::CompressionLevel compression_level,
+                          bool only_used_sources,
+                          Progress* progress)
+{
+       if (dest.empty () || name.empty ()) {
+               return -1;
+       }
+
+       /* We are going to temporarily change some source properties,
+        * don't allow any concurrent saves (periodic or otherwise */
+       Glib::Threads::Mutex::Lock lm (save_source_lock);
+
+       disable_record (false);
+
+       /* save current values */
+       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 + session_archive_suffix);
+
+       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>, std::string> orig_origin;
+       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);
+
+               /* build a list of used names */
+               std::set<std::string> audio_file_names;
+               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;
+                               }
+                       }
+                       audio_file_names.insert (Glib::path_get_basename (afs->path()));
+               }
+
+               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 {
+                               /* copy files as-is */
+                               if (!afs->within_session()) {
+                                       string to = Glib::path_get_basename (from);
+
+                                       /* avoid name collitions, see also new_audio_source_path_for_embedded ()
+                                        * - avoid conflict with files existing in interchange
+                                        * - avoid conflict with other embedded sources
+                                        */
+                                       if (audio_file_names.find (to) == audio_file_names.end ()) {
+                                               // we need a new name, add a '-<num>' before the '.<ext>'
+                                               string bn   = to.substr (0, to.find_last_of ('.'));
+                                               string ext  = to.find_last_of ('.') == string::npos ? "" : to.substr (to.find_last_of ('.'));
+                                               to = bn + "-1" + ext;
+                                       }
+                                       while (audio_file_names.find (to) == audio_file_names.end ()) {
+                                               to = bump_name_once (to, '-');
+                                       }
+
+                                       audio_file_names.insert (to);
+                                       filemap[from] = make_new_audio_path (to, name, name);
+
+                                       remove_dir_from_search_path (Glib::path_get_dirname (from), DataType::AUDIO);
+
+                                       orig_origin[afs] = afs->origin ();
+                                       afs->set_origin ("");
+
+                               } else {
+                                       filemap[from] = make_new_media_path (from, name, name);
+                               }
+                       }
+               }
+       }
+
+       /* 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);
+
+                       std::string channelsuffix = "";
+                       if (afs->channel() > 0) {  /* n_channels() is /wrongly/ 1. */
+                               /* embedded external multi-channel files are converted to multiple-mono */
+                               channelsuffix = string_compose ("-c%1", afs->channel ());
+                       }
+                       new_path = Glib::build_filename (Glib::path_get_dirname (new_path), PBD::basename_nosuffix (new_path) + channelsuffix + ".flac");
+                       g_mkdir_with_parents (Glib::path_get_dirname (new_path).c_str (), 0755);
+
+                       /* avoid name collisions of external files with same name */
+                       if (Glib::file_test (new_path, Glib::FILE_TEST_EXISTS)) {
+                               new_path = Glib::build_filename (Glib::path_get_dirname (new_path), PBD::basename_nosuffix (new_path) + channelsuffix + "-1.flac");
+                       }
+                       while (Glib::file_test (new_path, Glib::FILE_TEST_EXISTS)) {
+                               new_path = bump_name_once (new_path, '-');
+                       }
+
+                       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);
+
+       save_state (name, false, false, false, true, only_used_sources);
+
+       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;
+       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_origin.begin (); i != orig_origin.end (); ++i) {
+               i->first->set_origin (i->second);
+       }
+       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, compression_level);
+       remove_directory (to_dir);
+
+       return rv;
+}
+
 void
 Session::undo (uint32_t n)
 {