Use XMLNode::get_property in Session::restore_history
[ardour.git] / libs / ardour / session_state.cc
index 9df6754ba8455ec6155f57b40224fbb4b684f175..c59ae00a575f51ce12272a78b213c5a34f80f4d0 100644 (file)
@@ -29,6 +29,7 @@
 #include <cerrno>
 #include <cstdio> /* snprintf(3) ... grrr */
 #include <cmath>
+
 #include <unistd.h>
 #include <climits>
 #include <signal.h>
@@ -38,7 +39,7 @@
 #include <sys/vfs.h>
 #endif
 
-#ifdef __APPLE__
+#if defined(__APPLE__) || defined(__FreeBSD__)
 #include <sys/param.h>
 #include <sys/mount.h>
 #endif
 
 #include "evoral/SMF.hpp"
 
-#include "pbd/boost_debug.h"
 #include "pbd/basename.h"
-#include "pbd/controllable_descriptor.h"
 #include "pbd/debug.h"
 #include "pbd/enumwriter.h"
 #include "pbd/error.h"
+#include "pbd/file_archive.h"
 #include "pbd/file_utils.h"
 #include "pbd/pathexpand.h"
 #include "pbd/pthread_utils.h"
 #include "pbd/stacktrace.h"
-#include "pbd/convert.h"
+#include "pbd/types_convert.h"
 #include "pbd/localtime_r.h"
 #include "pbd/unwind.h"
 
 #include "ardour/audioengine.h"
 #include "ardour/audiofilesource.h"
 #include "ardour/audioregion.h"
+#include "ardour/auditioner.h"
 #include "ardour/automation_control.h"
+#include "ardour/boost_debug.h"
 #include "ardour/butler.h"
+#include "ardour/controllable_descriptor.h"
 #include "ardour/control_protocol_manager.h"
 #include "ardour/directory_names.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/region_factory.h"
+#include "ardour/revision.h"
 #include "ardour/route_group.h"
 #include "ardour/send.h"
 #include "ardour/session.h"
 #include "ardour/template_utils.h"
 #include "ardour/tempo.h"
 #include "ardour/ticker.h"
+#include "ardour/types_convert.h"
 #include "ardour/user_bundle.h"
+#include "ardour/vca.h"
+#include "ardour/vca_manager.h"
 
 #include "control_protocol/control_protocol.h"
 
-#include "i18n.h"
+#include "LuaBridge/LuaBridge.h"
+
+#include "pbd/i18n.h"
 #include <locale.h>
 
 using namespace std;
@@ -180,14 +193,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 */
 
@@ -221,16 +234,18 @@ Session::post_engine_init ()
        msc->set_input_port (boost::dynamic_pointer_cast<MidiPort>(scene_input_port()));
        msc->set_output_port (boost::dynamic_pointer_cast<MidiPort>(scene_output_port()));
 
-       boost::function<framecnt_t(void)> timer_func (boost::bind (&Session::audible_frame, this));
+       boost::function<framecnt_t(void)> timer_func (boost::bind (&Session::audible_frame, this, (bool*)(0)));
        boost::dynamic_pointer_cast<AsyncMIDIPort>(scene_input_port())->set_timer (timer_func);
 
        setup_midi_machine_control ();
 
        if (_butler->start_thread()) {
+               error << _("Butler did not start") << endmsg;
                return -1;
        }
 
        if (start_midi_thread ()) {
+               error << _("MIDI I/O thread did not start") << endmsg;
                return -1;
        }
 
@@ -246,9 +261,11 @@ Session::post_engine_init ()
                delete _tempo_map;
                _tempo_map = new TempoMap (_current_frame_rate);
                _tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
+               _tempo_map->MetricPositionChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
 
                /* MidiClock requires a tempo map */
 
+               delete midi_clock;
                midi_clock = new MidiClockTicker ();
                midi_clock->set_session (this);
 
@@ -256,6 +273,7 @@ Session::post_engine_init ()
 
                SndFileSource::setup_standard_crossfades (*this, frame_rate());
                _engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this));
+               _engine.MidiSelectionPortsChanged.connect_same_thread (*this, boost::bind (&Session::rewire_midi_selection_ports, this));
 
                AudioDiskstream::allocate_working_buffers();
                refresh_disk_space ();
@@ -266,6 +284,7 @@ Session::post_engine_init ()
 
                if (state_tree) {
                        if (set_state (*state_tree->root(), Stateful::loading_state_version)) {
+                               error << _("Could not set session state from XML") << endmsg;
                                return -1;
                        }
                } else {
@@ -281,7 +300,7 @@ Session::post_engine_init ()
 
                Config->map_parameters (ff);
                config.map_parameters (ft);
-                _butler->map_parameters ();
+               _butler->map_parameters ();
 
                /* Reset all panners */
 
@@ -332,7 +351,11 @@ Session::post_engine_init ()
                /* handle this one in a different way than all others, so that its clear what happened */
                error << err.what() << endmsg;
                return -1;
+       } catch (std::exception const & e) {
+               error << _("Unexpected exception during session setup: ") << e.what() << endmsg;
+               return -1;
        } catch (...) {
+               error << _("Unknown exception during session setup") << endmsg;
                return -1;
        }
 
@@ -606,37 +629,37 @@ 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 Control Out if necessary */
+       /* set up Master Out and Monitor Out if necessary */
 
-        if (bus_profile) {
+       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"), Route::MasterOut, DataType::AUDIO));
-                        if (r->init ()) {
-                                return -1;
-                        }
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                       // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
-#endif
+               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);
@@ -651,14 +674,14 @@ Session::create (const string& session_template, BusProfile* bus_profile)
                }
 
                if (!rl.empty()) {
-                       add_routes (rl, false, false, false);
+                       add_routes (rl, false, false, false, PresentationInfo::max_order);
                }
 
                // Waves Tracks: Skip this. Always use autoconnection for Tracks
                if (!ARDOUR::Profile->get_trx()) {
 
                        /* this allows the user to override settings with an environment variable.
-                        */
+                       */
 
                        if (no_auto_connect()) {
                                bus_profile->input_ac = AutoConnectOption (0);
@@ -668,7 +691,7 @@ Session::create (const string& session_template, BusProfile* bus_profile)
                        Config->set_input_auto_connect (bus_profile->input_ac);
                        Config->set_output_auto_connect (bus_profile->output_ac);
                }
-        }
+       }
 
        if (Config->get_use_monitor_bus() && bus_profile) {
                add_monitor_section ();
@@ -680,9 +703,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
@@ -756,6 +779,8 @@ Session::remove_state (string snapshot_name)
 int
 Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot, bool template_only)
 {
+       DEBUG_TRACE (DEBUG::Locale, string_compose ("Session::save_state locale '%1'\n", setlocale (LC_NUMERIC, NULL)));
+
        XMLTree tree;
        std::string xml_path(_session_dir->root_path());
 
@@ -774,12 +799,15 @@ 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)
+               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;
        }
 
+#ifndef NDEBUG
+       const int64_t save_start_time = g_get_monotonic_time();
+#endif
+
        /* tell sources we're saving first, in case they write out to a new file
         * which should be saved with the state rather than the old one */
        for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
@@ -877,6 +905,10 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
                StateSaved (snapshot_name); /* EMIT SIGNAL */
        }
 
+#ifndef NDEBUG
+       const int64_t elapsed_time_us = g_get_monotonic_time() - save_start_time;
+       cerr << "saved state in " << fixed << setprecision (1) << elapsed_time_us / 1000. << " ms\n";
+#endif
        return 0;
 }
 
@@ -907,7 +939,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;
                }
@@ -920,10 +952,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;
 
@@ -938,7 +970,7 @@ Session::load_state (string snapshot_name)
                return -1;
        }
 
-       XMLNode& root (*state_tree->root());
+       XMLNode const & root (*state_tree->root());
 
        if (root.name() != X_("Session")) {
                error << string_compose (_("Session file %1 is not a session"), xmlpath) << endmsg;
@@ -947,22 +979,21 @@ Session::load_state (string snapshot_name)
                return -1;
        }
 
-       const XMLProperty* prop;
-
-       if ((prop = root.property ("version")) == 0) {
-               /* no version implies very old version of Ardour */
-               Stateful::loading_state_version = 1000;
-       } else {
-               if (prop->value().find ('.') != string::npos) {
+       std::string version;
+       if (root.get_property ("version", version)) {
+               if (version.find ('.') != string::npos) {
                        /* old school version format */
-                       if (prop->value()[0] == '2') {
+                       if (version[0] == '2') {
                                Stateful::loading_state_version = 2000;
                        } else {
                                Stateful::loading_state_version = 3000;
                        }
                } else {
-                       Stateful::loading_state_version = atoi (prop->value());
+                       Stateful::loading_state_version = string_to<int32_t>(version);
                }
+       } else {
+               /* no version implies very old version of Ardour */
+               Stateful::loading_state_version = 1000;
        }
 
        if (Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION && _writable) {
@@ -991,7 +1022,7 @@ Session::load_state (string snapshot_name)
 int
 Session::load_options (const XMLNode& node)
 {
-       LocaleGuard lg (X_("C"));
+       LocaleGuard lg;
        config.set_variables (node);
        return 0;
 }
@@ -1021,23 +1052,101 @@ Session::get_template()
        return state(false);
 }
 
+typedef std::set<boost::shared_ptr<Playlist> > PlaylistSet;
+typedef std::set<boost::shared_ptr<Source> > SourceSet;
+
+bool
+Session::export_track_state (boost::shared_ptr<RouteList> rl, const string& path)
+{
+       if (Glib::file_test (path, Glib::FILE_TEST_EXISTS))  {
+               return false;
+       }
+       if (g_mkdir_with_parents (path.c_str(), 0755) != 0) {
+               return false;
+       }
+
+       PBD::Unwinder<std::string> uw (_template_state_dir, path);
+
+       LocaleGuard lg;
+       XMLNode* node = new XMLNode("TrackState"); // XXX
+       XMLNode* child;
+
+       PlaylistSet playlists; // SessionPlaylists
+       SourceSet sources;
+
+       // these will work with  new_route_from_template()
+       // TODO: LV2 plugin-state-dir needs to be relative (on load?)
+       child = node->add_child ("Routes");
+       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+               if ((*i)->is_auditioner()) {
+                       continue;
+               }
+               if ((*i)->is_master() || (*i)->is_monitor()) {
+                       continue;
+               }
+               child->add_child_nocopy ((*i)->get_state());
+               boost::shared_ptr<Track> track = boost::dynamic_pointer_cast<Track> (*i);
+               if (track) {
+                       playlists.insert (track->playlist ());
+               }
+       }
+
+       // on load, Regions in the playlists need to resolve and map Source-IDs
+       // also playlist needs to be merged or created with new-name..
+       // ... and Diskstream in tracks adjusted to use the correct playlist
+       child = node->add_child ("Playlists"); // SessionPlaylists::add_state
+       for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
+               child->add_child_nocopy ((*i)->get_state ());
+               boost::shared_ptr<RegionList> prl = (*i)->region_list ();
+               for (RegionList::const_iterator s = prl->begin(); s != prl->end(); ++s) {
+                       const Region::SourceList& sl = (*s)->sources ();
+                       for (Region::SourceList::const_iterator sli = sl.begin(); sli != sl.end(); ++sli) {
+                               sources.insert (*sli);
+                       }
+               }
+       }
+
+       child = node->add_child ("Sources");
+       for (SourceSet::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+               child->add_child_nocopy ((*i)->get_state ());
+               boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (*i);
+               if (fs) {
+#ifdef PLATFORM_WINDOWS
+                       fs->close ();
+#endif
+                       string p = fs->path ();
+                       PBD::copy_file (p, Glib::build_filename (path, Glib::path_get_basename (p)));
+               }
+       }
+
+       std::string sn = Glib::build_filename (path, "share.axml");
+
+       XMLTree tree;
+       tree.set_root (node);
+       return tree.write (sn.c_str());
+}
+
 XMLNode&
 Session::state (bool full_state)
 {
+       LocaleGuard lg;
        XMLNode* node = new XMLNode("Session");
        XMLNode* child;
 
-       char buf[16];
-       snprintf(buf, sizeof(buf), "%d", CURRENT_SESSION_FILE_VERSION);
-       node->add_property("version", buf);
+       node->set_property("version", CURRENT_SESSION_FILE_VERSION);
+
+       child = node->add_child ("ProgramVersion");
+       child->set_property("created-with", created_with);
+
+       std::string modified_with = string_compose ("%1 %2", PROGRAM_NAME, revision);
+       child->set_property("modified-with", modified_with);
 
        /* store configuration settings */
 
        if (full_state) {
 
-               node->add_property ("name", _name);
-               snprintf (buf, sizeof (buf), "%" PRId64, _nominal_frame_rate);
-               node->add_property ("sample-rate", buf);
+               node->set_property ("name", _name);
+               node->set_property ("sample-rate", _base_frame_rate);
 
                if (session_dirs.size() > 1) {
 
@@ -1069,15 +1178,21 @@ Session::state (bool full_state)
                }
        }
 
+       node->set_property ("end-is-free", _session_range_end_is_free);
+
        /* save the ID counter */
 
-       snprintf (buf, sizeof (buf), "%" PRIu64, ID::counter());
-       node->add_property ("id-counter", buf);
+       node->set_property ("id-counter", ID::counter());
+
+       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 */
+
+       node->set_property ("vca-counter", VCA::get_next_vca_number());
 
        /* various options */
 
@@ -1090,7 +1205,14 @@ Session::state (bool full_state)
                node->add_child_nocopy (*midi_port_stuff);
        }
 
-       node->add_child_nocopy (config.get_variables ());
+       XMLNode& cfgxml (config.get_variables ());
+       if (!full_state) {
+               /* exclude search-paths from template */
+               cfgxml.remove_nodes_and_delete ("name", "audio-search-path");
+               cfgxml.remove_nodes_and_delete ("name", "midi-search-path");
+               cfgxml.remove_nodes_and_delete ("name", "raid-path");
+       }
+       node->add_child_nocopy (cfgxml);
 
        node->add_child_nocopy (ARDOUR::SessionMetadata::Metadata()->get_state());
 
@@ -1104,7 +1226,7 @@ Session::state (bool full_state)
                        /* Don't save information about non-file Sources, or
                         * about non-destructive file sources that are empty
                         * and unused by any regions.
-                        */
+                        */
 
                        boost::shared_ptr<FileSource> fs;
 
@@ -1125,18 +1247,18 @@ Session::state (bool full_state)
 
        if (full_state) {
                Glib::Threads::Mutex::Lock rl (region_lock);
-                const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
-                for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
-                        boost::shared_ptr<Region> r = i->second;
-                        /* only store regions not attached to playlists */
-                        if (r->playlist() == 0) {
+               const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
+               for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
+                       boost::shared_ptr<Region> r = i->second;
+                       /* only store regions not attached to playlists */
+                       if (r->playlist() == 0) {
                                if (boost::dynamic_pointer_cast<AudioRegion>(r)) {
                                        child->add_child_nocopy ((boost::dynamic_pointer_cast<AudioRegion>(r))->get_basic_state ());
                                } else {
                                        child->add_child_nocopy (r->get_state ());
                                }
-                        }
-                }
+                       }
+               }
 
                RegionFactory::CompoundAssociations& cassocs (RegionFactory::compound_associations());
 
@@ -1144,19 +1266,14 @@ Session::state (bool full_state)
                        XMLNode* ca = node->add_child (X_("CompoundAssociations"));
 
                        for (RegionFactory::CompoundAssociations::iterator i = cassocs.begin(); i != cassocs.end(); ++i) {
-                               char buf[64];
                                XMLNode* can = new XMLNode (X_("CompoundAssociation"));
-                               i->first->id().print (buf, sizeof (buf));
-                               can->add_property (X_("copy"), buf);
-                               i->second->id().print (buf, sizeof (buf));
-                               can->add_property (X_("original"), buf);
+                               can->set_property (X_("copy"), i->first->id());
+                               can->set_property (X_("original"), i->second->id());
                                ca->add_child_nocopy (*can);
                        }
                }
        }
 
-
-
        if (full_state) {
 
                if (_locations) {
@@ -1164,9 +1281,10 @@ Session::state (bool full_state)
                }
        } else {
                Locations loc (*this);
+               const bool was_dirty = dirty();
                // for a template, just create a new Locations, populate it
                // with the default start and end, and get the state for that.
-               Location* range = new Location (*this, 0, 0, _("session"), Location::IsSessionRange);
+               Location* range = new Location (*this, 0, 0, _("session"), Location::IsSessionRange, 0);
                range->set (max_framepos, 0);
                loc.add (range);
                XMLNode& locations_state = loc.get_state();
@@ -1180,6 +1298,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");
@@ -1193,6 +1320,8 @@ Session::state (bool full_state)
                }
        }
 
+       node->add_child_nocopy (_vca_manager->get_state());
+
        child = node->add_child ("Routes");
        {
                boost::shared_ptr<RouteList> r = routes.reader ();
@@ -1201,7 +1330,7 @@ Session::state (bool full_state)
                RouteList public_order (*r);
                public_order.sort (cmp);
 
-               /* the sort should have put control outs first */
+               /* the sort should have put the monitor out first */
 
                if (_monitor_out) {
                        assert (_monitor_out == public_order.front());
@@ -1241,7 +1370,7 @@ Session::state (bool full_state)
                ltc_output_child->add_child_nocopy (_ltc_output->state (full_state));
        }
 
-        node->add_child_nocopy (_speakers->get_state());
+       node->add_child_nocopy (_speakers->get_state());
        node->add_child_nocopy (_tempo_map->get_state());
        node->add_child_nocopy (get_control_protocol_state());
 
@@ -1249,6 +1378,26 @@ Session::state (bool full_state)
                node->add_child_copy (*_extra_xml);
        }
 
+       {
+               Glib::Threads::Mutex::Lock lm (lua_lock);
+               std::string saved;
+               {
+                       luabridge::LuaRef savedstate ((*_lua_save)());
+                       saved = savedstate.cast<std::string>();
+               }
+               lua.collect_garbage ();
+               lm.release ();
+
+               gchar* b64 = g_base64_encode ((const guchar*)saved.c_str (), saved.size ());
+               std::string b64s (b64);
+               g_free (b64);
+
+               XMLNode* script_node = new XMLNode (X_("Script"));
+               script_node->set_property (X_("lua"), LUA_VERSION);
+               script_node->add_content (b64s);
+               node->add_child_nocopy (*script_node);
+       }
+
        return *node;
 }
 
@@ -1262,9 +1411,9 @@ Session::get_control_protocol_state ()
 int
 Session::set_state (const XMLNode& node, int version)
 {
+       LocaleGuard lg;
        XMLNodeList nlist;
        XMLNode* child;
-       const XMLProperty* prop;
        int ret = -1;
 
        _state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave);
@@ -1274,42 +1423,56 @@ Session::set_state (const XMLNode& node, int version)
                goto out;
        }
 
-       if ((prop = node.property ("name")) != 0) {
-               _name = prop->value ();
-       }
+       node.get_property ("name", _name);
 
-       if ((prop = node.property (X_("sample-rate"))) != 0) {
+       if (node.get_property (X_("sample-rate"), _base_frame_rate)) {
 
-               _nominal_frame_rate = atoi (prop->value());
+               _nominal_frame_rate = _base_frame_rate;
 
-               if (_nominal_frame_rate != _current_frame_rate) {
-                        boost::optional<int> r = AskAboutSampleRateMismatch (_nominal_frame_rate, _current_frame_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 (r.get_value_or (0)) {
                                goto out;
                        }
                }
        }
 
+       created_with = "unknown";
+       if ((child = find_named_node (node, "ProgramVersion")) != 0) {
+               child->get_property (X_("created-with"), created_with);
+       }
+
        setup_raid_path(_session_dir->root_path());
 
-       if ((prop = node.property (X_("id-counter"))) != 0) {
-               uint64_t x;
-               sscanf (prop->value().c_str(), "%" PRIu64, &x);
-               ID::init_counter (x);
+       node.get_property (X_("end-is-free"), _session_range_end_is_free);
+
+       uint64_t counter;
+       if (node.get_property (X_("id-counter"), counter)) {
+               ID::init_counter (counter);
        } else {
                /* old sessions used a timebased counter, so fake
-                  the startup ID counter based on a standard
-                  timestamp.
-               */
+                * the startup ID counter based on a standard
+                * timestamp.
+                */
                time_t now;
                time (&now);
                ID::init_counter (now);
        }
 
-        if ((prop = node.property (X_("event-counter"))) != 0) {
-                Evoral::init_event_id_counter (atoi (prop->value()));
-        }
+       if (node.get_property (X_("name-counter"), counter)) {
+               init_name_id_counter (counter);
+       }
+
+       if (node.get_property (X_("event-counter"), counter)) {
+               Evoral::init_event_id_counter (counter);
+       }
 
+       if (node.get_property (X_("vca-counter"), counter)) {
+               VCA::set_next_vca_number (counter);
+       } else {
+               VCA::set_next_vca_number (1);
+       }
 
        if ((child = find_named_node (node, "MIDIPorts")) != 0) {
                _midi_ports->set_midi_port_states (child->children());
@@ -1335,9 +1498,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;
@@ -1398,8 +1561,8 @@ Session::set_state (const XMLNode& node, int version)
                        //goto out;
                } else {
                        /* We can't load Bundles yet as they need to be able
-                          to convert from port names to Port objects, which can't happen until
-                          later */
+                        * to convert from port names to Port objects, which can't happen until
+                        * later */
                        _bundle_xml_node = new XMLNode (*child);
                }
        }
@@ -1413,6 +1576,10 @@ Session::set_state (const XMLNode& node, int version)
                }
        }
 
+       if ((child = find_named_node (node, VCAManager::xml_node_name)) != 0) {
+               _vca_manager->set_state (*child, version);
+       }
+
        if ((child = find_named_node (node, "Routes")) == 0) {
                error << _("Session: XML state has no routes section") << endmsg;
                goto out;
@@ -1420,6 +1587,10 @@ Session::set_state (const XMLNode& node, int version)
                goto out;
        }
 
+       /* Now that we have Routes and masters loaded, connect them if appropriate */
+
+       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 ();
 
@@ -1459,6 +1630,21 @@ Session::set_state (const XMLNode& node, int version)
                ControlProtocolManager::instance().set_state (*child, version);
        }
 
+       if ((child = find_named_node (node, "Script"))) {
+               for (XMLNodeList::const_iterator n = child->children ().begin (); n != child->children ().end (); ++n) {
+                       if (!(*n)->is_content ()) { continue; }
+                       gsize size;
+                       guchar* buf = g_base64_decode ((*n)->content ().c_str (), &size);
+                       try {
+                               Glib::Threads::Mutex::Lock lm (lua_lock);
+                               (*_lua_load)(std::string ((const char*)buf, size));
+                       } catch (luabridge::LuaException const& e) {
+                               cerr << "LuaException:" << e.what () << endl;
+                       }
+                       g_free (buf);
+               }
+       }
+
        update_route_record_state ();
 
        /* here beginneth the second phase ... */
@@ -1470,7 +1656,7 @@ Session::set_state (const XMLNode& node, int version)
        state_tree = 0;
        return 0;
 
-  out:
+out:
        delete state_tree;
        state_tree = 0;
        return ret;
@@ -1508,7 +1694,7 @@ Session::load_routes (const XMLNode& node, int version)
 
        BootMessage (_("Tracks/busses loaded;  Adding to Session"));
 
-       add_routes (new_routes, false, false, false);
+       add_routes (new_routes, false, false, false, PresentationInfo::max_order);
 
        BootMessage (_("Finished adding tracks/busses"));
 
@@ -1527,11 +1713,7 @@ Session::XMLRouteFactory (const XMLNode& node, int version)
        XMLNode* ds_child = find_named_node (node, X_("Diskstream"));
 
        DataType type = DataType::AUDIO;
-       const XMLProperty* prop = node.property("default-type");
-
-       if (prop) {
-               type = DataType (prop->value());
-       }
+       node.get_property("default-type", type);
 
        assert (type != DataType::NIL);
 
@@ -1539,40 +1721,31 @@ Session::XMLRouteFactory (const XMLNode& node, int version)
 
                boost::shared_ptr<Track> track;
 
-                if (type == DataType::AUDIO) {
-                        track.reset (new AudioTrack (*this, X_("toBeResetFroXML")));
-                } else {
-                        track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
-                }
+               if (type == DataType::AUDIO) {
+                       track.reset (new AudioTrack (*this, X_("toBeResetFroXML")));
+               } else {
+                       track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
+               }
 
-                if (track->init()) {
-                        return ret;
-                }
+               if (track->init()) {
+                       return ret;
+               }
 
-                if (track->set_state (node, version)) {
-                        return ret;
-                }
+               if (track->set_state (node, version)) {
+                       return ret;
+               }
 
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                // boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
-#endif
-                ret = track;
+               BOOST_MARK_TRACK (track);
+               ret = track;
 
        } else {
-               enum Route::Flag flags = Route::Flag(0);
-               const XMLProperty* prop = node.property("flags");
-               if (prop) {
-                       flags = Route::Flag (string_2_enum (prop->value(), flags));
-               }
-
+               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) {
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                        // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
-#endif
-                        ret = r;
-                }
+               if (r->init () == 0 && r->set_state (node, version) == 0) {
+                       BOOST_MARK_ROUTE (r);
+                       ret = r;
+               }
        }
 
        return ret;
@@ -1593,11 +1766,7 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version)
        }
 
        DataType type = DataType::AUDIO;
-       const XMLProperty* prop = node.property("default-type");
-
-       if (prop) {
-               type = DataType (prop->value());
-       }
+       node.get_property("default-type", type);
 
        assert (type != DataType::NIL);
 
@@ -1615,42 +1784,33 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version)
 
                boost::shared_ptr<Track> track;
 
-                if (type == DataType::AUDIO) {
-                        track.reset (new AudioTrack (*this, X_("toBeResetFroXML")));
-                } else {
-                        track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
-                }
+               if (type == DataType::AUDIO) {
+                       track.reset (new AudioTrack (*this, X_("toBeResetFroXML")));
+               } else {
+                       track.reset (new MidiTrack (*this, X_("toBeResetFroXML")));
+               }
 
-                if (track->init()) {
-                        return ret;
-                }
+               if (track->init()) {
+                       return ret;
+               }
 
-                if (track->set_state (node, version)) {
-                        return ret;
-                }
+               if (track->set_state (node, version)) {
+                       return ret;
+               }
 
                track->set_diskstream (*i);
 
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                // boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
-#endif
-                ret = track;
+               BOOST_MARK_TRACK (track);
+               ret = track;
 
        } else {
-               enum Route::Flag flags = Route::Flag(0);
-               const XMLProperty* prop = node.property("flags");
-               if (prop) {
-                       flags = Route::Flag (string_2_enum (prop->value(), flags));
-               }
-
+               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) {
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
-                        // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
-#endif
-                        ret = r;
-                }
+               if (r->init () == 0 && r->set_state (node, version) == 0) {
+                       BOOST_MARK_ROUTE (r);
+                       ret = r;
+               }
        }
 
        return ret;
@@ -1670,7 +1830,7 @@ Session::load_regions (const XMLNode& node)
        for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
                if ((region = XMLRegionFactory (**niter, false)) == 0) {
                        error << _("Session: cannot create Region from XML description.");
-                       const XMLProperty *name = (**niter).property("name");
+                       XMLProperty const * name = (**niter).property("name");
 
                        if (name) {
                                error << " " << string_compose (_("Can not load state for region '%1'"), name->value());
@@ -1688,7 +1848,7 @@ Session::load_compounds (const XMLNode& node)
 {
        XMLNodeList calist = node.children();
        XMLNodeConstIterator caiter;
-       XMLProperty *caprop;
+       XMLProperty const * caprop;
 
        for (caiter = calist.begin(); caiter != calist.end(); ++caiter) {
                XMLNode* ca = *caiter;
@@ -1735,7 +1895,7 @@ Session::load_nested_sources (const XMLNode& node)
                        /* it may already exist, so don't recreate it unnecessarily
                         */
 
-                       XMLProperty* prop = (*niter)->property (X_("id"));
+                       XMLProperty const * prop = (*niter)->property (X_("id"));
                        if (!prop) {
                                error << _("Nested source has no ID info in session file! (ignored)") << endmsg;
                                continue;
@@ -1759,7 +1919,7 @@ Session::load_nested_sources (const XMLNode& node)
 boost::shared_ptr<Region>
 Session::XMLRegionFactory (const XMLNode& node, bool full)
 {
-       const XMLProperty* type = node.property("type");
+       XMLProperty const * type = node.property("type");
 
        try {
 
@@ -1772,11 +1932,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> ();
@@ -1788,7 +1948,7 @@ Session::XMLRegionFactory (const XMLNode& node, bool full)
 boost::shared_ptr<AudioRegion>
 Session::XMLAudioRegionFactory (const XMLNode& node, bool /*full*/)
 {
-       const XMLProperty* prop;
+       XMLProperty const * prop;
        boost::shared_ptr<Source> source;
        boost::shared_ptr<AudioSource> as;
        SourceList sources;
@@ -1800,9 +1960,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";
@@ -1907,7 +2065,7 @@ Session::XMLAudioRegionFactory (const XMLNode& node, bool /*full*/)
 boost::shared_ptr<MidiRegion>
 Session::XMLMidiRegionFactory (const XMLNode& node, bool /*full*/)
 {
-       const XMLProperty* prop;
+       XMLProperty const * prop;
        boost::shared_ptr<Source> source;
        boost::shared_ptr<MidiSource> ms;
        SourceList sources;
@@ -1997,90 +2155,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;
-                        }
+                               user_choice = -2;
+                       }
 
-                        switch (user_choice) {
-                        case 0:
-                                /* user added a new search location, so try again */
-                                goto retry;
+                       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 1:
+                                       /* user asked to quit the entire session load */
+                                       return -1;
 
-                        case 2:
-                                no_questions_about_missing_files = true;
-                                goto retry;
+                               case 2:
+                                       no_questions_about_missing_files = true;
+                                       goto retry;
 
-                        case 3:
-                                no_questions_about_missing_files = true;
-                                /* fallthru */
+                               case 3:
+                                       no_questions_about_missing_files = true;
+                                       /* fallthru */
 
-                        case -1:
-                        default:
-                               switch (err.type) {
+                               case -1:
+                               default:
+                                       switch (err.type) {
 
-                               case DataType::AUDIO:
-                                       source = SourceFactory::createSilent (*this, **niter, max_framecnt, _current_frame_rate);
-                                       break;
+                                               case DataType::AUDIO:
+                                                       source = SourceFactory::createSilent (*this, **niter, max_framecnt, _current_frame_rate);
+                                                       break;
 
-                               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;
+                                               case DataType::MIDI:
+                                                       /* The MIDI file is actually missing so
+                                                        * just create a new one in the same
+                                                        * location. Do not announce its
+                                                        */
+                                                       string fullpath;
+
+                                                       if (!Glib::path_is_absolute (err.path)) {
+                                                               fullpath = Glib::build_filename (source_search_path (DataType::MIDI).front(), err.path);
+                                                       } else {
+                                                               /* this should be an unrecoverable error: we would be creating a MIDI file outside
+                                                                * the session tree.
+                                                                */
+                                                               return -1;
+                                                       }
+                                                       /* Note that we do not announce the source just yet - we need to reset its ID before we do that */
+                                                       source = SourceFactory::createWritable (DataType::MIDI, *this, fullpath, false, _current_frame_rate, false, false);
+                                                       /* reset ID to match the missing one */
+                                                       source->set_id (**niter);
+                                                       /* Now we can announce it */
+                                                       SourceFactory::SourceCreated (source);
+                                                       break;
                                        }
-                                       /* Note that we do not announce the source just yet - we need to reset its ID before we do that */
-                                       source = SourceFactory::createWritable (DataType::MIDI, *this, fullpath, false, _current_frame_rate, false, false);
-                                       /* reset ID to match the missing one */
-                                       source->set_id (**niter);
-                                       /* Now we can announce it */
-                                       SourceFactory::SourceCreated (source);
                                        break;
-                               }
-                                break;
-                        }
+                       }
                }
        }
 
@@ -2180,7 +2383,7 @@ Session::save_template (string template_name, bool replace_existing)
 void
 Session::refresh_disk_space ()
 {
-#if __APPLE__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
+#if __APPLE__ || __FreeBSD__ || __NetBSD__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
 
        Glib::Threads::Mutex::Lock lm (space_lock);
 
@@ -2190,10 +2393,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 */
@@ -2227,9 +2435,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;
@@ -2493,6 +2701,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)
 {
@@ -2501,7 +2728,7 @@ Session::add_route_group (RouteGroup* g)
 
        g->RouteAdded.connect_same_thread (*this, boost::bind (&Session::route_added_to_route_group, this, _1, _2));
        g->RouteRemoved.connect_same_thread (*this, boost::bind (&Session::route_removed_from_route_group, this, _1, _2));
-       g->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::route_group_property_changed, this, g, _1));
+       g->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::route_group_property_changed, this, g));
 
        set_dirty ();
 }
@@ -2548,7 +2775,7 @@ Session::route_group_by_name (string name)
 RouteGroup&
 Session::all_route_group() const
 {
-        return *_all_route_group;
+       return *_all_route_group;
 }
 
 void
@@ -2569,6 +2796,15 @@ Session::add_command (Command* const cmd)
                            cmd->name ()));
        _current_trans->add_command (cmd);
 }
+
+PBD::StatefulDiffCommand*
+Session::add_stateful_diff_command (boost::shared_ptr<PBD::StatefulDestructible> sfd)
+{
+       PBD::StatefulDiffCommand* cmd = new PBD::StatefulDiffCommand (sfd);
+       add_command (cmd);
+       return cmd;
+}
+
 void
 Session::begin_reversible_command (const string& name)
 {
@@ -2669,27 +2905,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
@@ -2730,7 +2966,7 @@ Session::find_all_sources (string path, set<string>& result)
 
        for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
 
-               XMLProperty* prop;
+               XMLProperty const * prop;
 
                if ((prop = (*niter)->property (X_("type"))) == 0) {
                        continue;
@@ -2781,14 +3017,17 @@ Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_th
                return 0;
        }
 
-       this_snapshot_path = _path;
-       this_snapshot_path += legalize_for_path (_current_snapshot_name);
+       this_snapshot_path = Glib::build_filename (_path, legalize_for_path (_current_snapshot_name));
        this_snapshot_path += statefile_suffix;
 
        for (vector<string>::iterator i = state_files.begin(); i != state_files.end(); ++i) {
 
+               cerr << "Looking at snapshot " << (*i) << " ( with this = [" << this_snapshot_path << "])\n";
+
                if (exclude_this_snapshot && *i == this_snapshot_path) {
+                       cerr << "\texcluded\n";
                        continue;
+
                }
 
                if (find_all_sources (*i, result) < 0) {
@@ -2800,18 +3039,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);
 }
 
@@ -2869,7 +3108,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;
        }
@@ -2919,6 +3158,12 @@ Session::cleanup_peakfiles ()
        return 0;
 }
 
+static void
+merge_all_sources (boost::shared_ptr<const Playlist> pl, std::set<boost::shared_ptr<Source> >* all_sources)
+{
+       pl->deep_sources (*all_sources);
+}
+
 int
 Session::cleanup_sources (CleanupReport& rep)
 {
@@ -2929,14 +3174,14 @@ Session::cleanup_sources (CleanupReport& rep)
        string midi_path;
        vector<string> candidates;
        vector<string> unused;
-       set<string> all_sources;
-       bool used;
+       set<string> sources_used_by_all_snapshots;
        string spath;
        int ret = -1;
        string tmppath1;
        string tmppath2;
        Searchpath asp;
        Searchpath msp;
+       set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
 
        _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
 
@@ -2958,10 +3203,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 */
 
@@ -2976,10 +3220,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 ();
                }
@@ -3007,74 +3251,98 @@ Session::cleanup_sources (CleanupReport& rep)
        find_files_matching_filter (candidates, audio_path, accept_all_audio_files, (void *) 0, true, true);
        find_files_matching_filter (candidates, midi_path, accept_all_midi_files, (void *) 0, true, true);
 
-       /* find all sources, but don't use this snapshot because the
-          state file on disk still references sources we may have already
-          dropped.
-       */
+       /* add sources from all other snapshots as "used", but don't use this
+                snapshot because the state file on disk still references sources we
+                may have already dropped.
+                */
 
-       find_all_sources_across_snapshots (all_sources, true);
+       find_all_sources_across_snapshots (sources_used_by_all_snapshots, true);
 
-       /*  add our current source list
+       /* Although the region factory has a list of all regions ever created
+        * for this session, we're only interested in regions actually in
+        * playlists right now. So merge all playlist regions lists together.
+        *
+        * This will include the playlists used within compound regions.
         */
 
+       playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot));
+
+       /*  add our current source list
+       */
+
        for (SourceMap::iterator i = sources.begin(); i != sources.end(); ) {
                boost::shared_ptr<FileSource> fs;
-                SourceMap::iterator tmp = i;
-                ++tmp;
+               SourceMap::iterator tmp = i;
+               ++tmp;
 
-               if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) != 0) {
+               if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) == 0) {
+                       /* not a file */
+                       i = tmp;
+                       continue;
+               }
 
-                       /* this is mostly for windows which doesn't allow file
-                        * renaming if the file is in use. But we don't special
-                        * case it because we need to know if this causes
-                        * problems, and the easiest way to notice that is to
-                        * keep it in place for all platforms.
-                        */
+               /* this is mostly for windows which doesn't allow file
+                * renaming if the file is in use. But we do not special
+                * case it because we need to know if this causes
+                * problems, and the easiest way to notice that is to
+                * keep it in place for all platforms.
+                */
 
-                       fs->close ();
+               fs->close ();
 
-                       if (!fs->is_stub()) {
+               if (!fs->is_stub()) {
 
-                               if (playlists->source_use_count (fs) != 0) {
-                                       all_sources.insert (fs->path());
-                               } else {
+                       /* Note that we're checking a list of all
+                        * sources across all snapshots with the list
+                        * of sources used by this snapshot.
+                        */
 
-                                       /* we might not remove this source from disk, because it may be used
-                                          by other snapshots, but its not being used in this version
-                                          so lets get rid of it now, along with any representative regions
-                                          in the region list.
-                                       */
+                       if (sources_used_by_this_snapshot.find (i->second) != sources_used_by_this_snapshot.end()) {
+                               /* this source is in use by this snapshot */
+                               sources_used_by_all_snapshots.insert (fs->path());
+                               cerr << "Source from source list found in used_by_this_snapshot (" << fs->path() << ")\n";
+                       } else {
+                               cerr << "Source from source list NOT found in used_by_this_snapshot (" << fs->path() << ")\n";
+                               /* this source is NOT in use by this snapshot */
 
-                                       RegionFactory::remove_regions_using_source (i->second);
+                               /* remove all related regions from RegionFactory master list */
 
-                                       // also remove source from all_sources
+                               RegionFactory::remove_regions_using_source (i->second);
 
-                                       for (set<string>::iterator j = all_sources.begin(); j != all_sources.end(); ++j) {
-                                               spath = Glib::path_get_basename (*j);
-                                               if (spath == i->second->name()) {
-                                                       all_sources.erase (j);
-                                                       break;
-                                               }
-                                       }
+                               /* remove from our current source list
+                                * also. We may not remove it from
+                                * disk, because it may be used by
+                                * other snapshots, but it isn't used inside this
+                                * snapshot anymore, so we don't need a
+                                * reference to it.
+                                */
 
-                                       sources.erase (i);
-                               }
+                               sources.erase (i);
                        }
                }
 
-                i = tmp;
+               i = tmp;
        }
 
+       /* now check each candidate source to see if it exists in the list of
+        * sources_used_by_all_snapshots. If it doesn't, put it into "unused".
+        */
+
+       cerr << "Candidates: " << candidates.size() << endl;
+       cerr << "Used by others: " << sources_used_by_all_snapshots.size() << endl;
+
        for (vector<string>::iterator x = candidates.begin(); x != candidates.end(); ++x) {
 
-               used = false;
+               bool used = false;
                spath = *x;
 
-               for (set<string>::iterator i = all_sources.begin(); i != all_sources.end(); ++i) {
+               for (set<string>::iterator i = sources_used_by_all_snapshots.begin(); i != sources_used_by_all_snapshots.end(); ++i) {
 
                        tmppath1 = canonical_path (spath);
                        tmppath2 = canonical_path ((*i));
 
+                       cerr << "\t => " << tmppath2 << endl;
+
                        if (tmppath1 == tmppath2) {
                                used = true;
                                break;
@@ -3086,6 +3354,14 @@ Session::cleanup_sources (CleanupReport& rep)
                }
        }
 
+       cerr << "Actually unused: " << unused.size() << endl;
+
+       if (unused.empty()) {
+               /* Nothing to do */
+               ret = 0;
+               goto out;
+       }
+
        /* now try to move all unused files into the "dead" directory(ies) */
 
        for (vector<string>::iterator x = unused.begin(); x != unused.end(); ++x) {
@@ -3094,9 +3370,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) {
 
@@ -3142,44 +3418,35 @@ Session::cleanup_sources (CleanupReport& rep)
 
                        if (version == 999) {
                                error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
-                                                 newpath)
-                                     << endmsg;
+                                               newpath)
+                                       << endmsg;
                        } else {
                                newpath = newpath_v;
                        }
 
-               } else {
-
-                       /* it doesn't exist, or we can't read it or something */
-
                }
 
-               g_stat ((*x).c_str(), &statbuf);
-
-               if (::rename ((*x).c_str(), newpath.c_str()) != 0) {
-                       error << string_compose (_("cannot rename unused file source from %1 to %2 (%3)"),
-                                         (*x), newpath, strerror (errno))
-                             << endmsg;
-                       goto out;
+               if ((g_stat ((*x).c_str(), &statbuf) != 0) || (::g_rename ((*x).c_str(), newpath.c_str()) != 0)) {
+                       error << string_compose (_("cannot rename unused file source from %1 to %2 (%3)"), (*x),
+                                       newpath, g_strerror (errno)) << endmsg;
+                       continue;
                }
 
-               /* see if there an easy to find peakfile for this file, and remove it.
-                */
+               /* see if there an easy to find peakfile for this file, and remove it.  */
 
-                string base = Glib::path_get_basename (*x);
-                base += "%A"; /* this is what we add for the channel suffix of all native files,
-                                 or for the first channel of embedded files. it will miss
-                                 some peakfiles for other channels
-                              */
+               string base = Glib::path_get_basename (*x);
+               base += "%A"; /* this is what we add for the channel suffix of all native files,
+                                                                        * or for the first channel of embedded files. it will miss
+                                                                        * some peakfiles for other channels
+                                                                        */
                string peakpath = construct_peak_filepath (base);
 
-               if (Glib::file_test (peakpath.c_str(), Glib::FILE_TEST_EXISTS)) {
-                       if (::g_unlink (peakpath.c_str()) != 0) {
-                               error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
-                                                         peakpath, _path, strerror (errno))
-                                     << endmsg;
+               if (Glib::file_test (peakpath.c_str (), Glib::FILE_TEST_EXISTS)) {
+                       if (::g_unlink (peakpath.c_str ()) != 0) {
+                               error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"), peakpath, _path,
+                                               g_strerror (errno)) << endmsg;
                                /* try to back out */
-                               ::rename (newpath.c_str(), _path.c_str());
+                               ::g_rename (newpath.c_str (), _path.c_str ());
                                goto out;
                        }
                }
@@ -3193,13 +3460,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;
@@ -3220,7 +3487,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;
@@ -3229,23 +3496,20 @@ Session::cleanup_trash_sources (CleanupReport& rep)
 void
 Session::set_dirty ()
 {
-       /* never mark session dirty during loading */
+       /* return early if there's nothing to do */
+       if (dirty ()) {
+               return;
+       }
 
+       /* never mark session dirty during loading */
        if (_state_of_the_state & Loading) {
                return;
        }
 
-       bool was_dirty = dirty();
-
        _state_of_the_state = StateOfTheState (_state_of_the_state | Dirty);
-
-
-       if (!was_dirty) {
-               DirtyChanged(); /* EMIT SIGNAL */
-       }
+       DirtyChanged(); /* EMIT SIGNAL */
 }
 
-
 void
 Session::set_clean ()
 {
@@ -3253,7 +3517,6 @@ Session::set_clean ()
 
        _state_of_the_state = Clean;
 
-
        if (was_dirty) {
                DirtyChanged(); /* EMIT SIGNAL */
        }
@@ -3320,75 +3583,86 @@ boost::shared_ptr<Controllable>
 Session::controllable_by_descriptor (const ControllableDescriptor& desc)
 {
        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") {
-                       r = _master_out;
-               } else if (str == "control" || str == "listen") {
-                       r = _monitor_out;
+                       s = _master_out;
+               } else if (str == "control" || str == "listen" || str == "monitor" || str == "Monitor") {
+                       s = _monitor_out;
+               } else if (str == "auditioner") {
+                       s = auditioner;
                } else {
-                       r = route_by_name (desc.top_level_name());
+                       s = route_by_name (desc.top_level_name());
                }
+
                break;
        }
 
-       case ControllableDescriptor::RemoteControlID:
-               r = route_by_remote_id (desc.rid());
+       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 (!r) {
+       if (!s) {
                return c;
        }
 
+       r = boost::dynamic_pointer_cast<Route> (s);
+
        switch (desc.subtype()) {
        case ControllableDescriptor::Gain:
-               c = r->gain_control ();
+               c = s->gain_control ();
                break;
 
        case ControllableDescriptor::Trim:
-               c = r->trim()->gain_control ();
+               c = s->trim_control ();
                break;
 
        case ControllableDescriptor::Solo:
-                c = r->solo_control();
+               c = s->solo_control();
                break;
 
        case ControllableDescriptor::Mute:
-               c = r->mute_control();
+               c = s->mute_control();
                break;
 
        case ControllableDescriptor::Recenable:
-       {
-               boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(r);
-
-               if (t) {
-                       c = t->rec_enable_control ();
-               }
+               c = s->rec_enable_control ();
                break;
-       }
 
        case ControllableDescriptor::PanDirection:
-        {
-                c = r->pannable()->pan_azimuth_control;
+               c = s->pan_azimuth_control();
                break;
-        }
 
        case ControllableDescriptor::PanWidth:
-        {
-                c = r->pannable()->pan_width_control;
+               c = s->pan_width_control();
                break;
-        }
 
        case ControllableDescriptor::PanElevation:
-        {
-                c = r->pannable()->pan_elevation_control;
+               c = s->pan_elevation_control();
                break;
-        }
 
        case ControllableDescriptor::Balance:
                /* XXX simple pan control */
@@ -3409,6 +3683,10 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                        --parameter_index;
                }
 
+               if (!r) {
+                       return c;
+               }
+
                boost::shared_ptr<Processor> p = r->nth_plugin (plugin);
 
                if (p) {
@@ -3418,26 +3696,15 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
                break;
        }
 
-       case ControllableDescriptor::SendGain:
-       {
+       case ControllableDescriptor::SendGain: {
                uint32_t send = desc.target (0);
-
-               /* revert to zero-based counting */
-
                if (send > 0) {
                        --send;
                }
-
-               boost::shared_ptr<Processor> p = r->nth_send (send);
-
-               if (p) {
-                       boost::shared_ptr<Send> s = boost::dynamic_pointer_cast<Send>(p);
-                       boost::shared_ptr<Amp> a = s->amp();
-
-                       if (a) {
-                               c = s->amp()->gain_control();
-                       }
+               if (!r) {
+                       return c;
                }
+               c = r->send_level_controllable (send);
                break;
        }
 
@@ -3464,6 +3731,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);
 }
 
@@ -3547,17 +3819,25 @@ Session::restore_history (string snapshot_name)
        // replace history
        _history.clear();
 
-       for (XMLNodeConstIterator it  = tree.root()->children().begin(); it != tree.root()->children().end(); it++) {
+       for (XMLNodeConstIterator it  = tree.root()->children().begin(); it != tree.root()->children().end(); ++it) {
 
                XMLNode *t = *it;
                UndoTransaction* ut = new UndoTransaction ();
-               struct timeval tv;
 
-               ut->set_name(t->property("name")->value());
-               stringstream ss(t->property("tv-sec")->value());
-               ss >> tv.tv_sec;
-               ss.str(t->property("tv-usec")->value());
-               ss >> tv.tv_usec;
+               std::string name;
+               int64_t tv_sec;
+               int64_t tv_usec;
+
+               if (!t->get_property ("name", name) || !t->get_property ("tv-sec", tv_sec) ||
+                   !t->get_property ("tv-usec", tv_usec)) {
+                       continue;
+               }
+
+               ut->set_name (name);
+
+               struct timeval tv;
+               tv.tv_sec = tv_sec;
+               tv.tv_usec = tv_usec;
                ut->set_timestamp(tv);
 
                for (XMLNodeConstIterator child_it  = t->children().begin();
@@ -3634,11 +3914,13 @@ Session::config_changed (std::string p, bool ours)
 
        } else if (p == "auto-loop") {
 
+       } else if (p == "session-monitoring") {
+
        } else if (p == "auto-input") {
 
                if (Config->get_monitoring_model() == HardwareMonitoring && transport_rolling()) {
                        /* auto-input only makes a difference if we're rolling */
-                        set_track_monitor_input_status (!config.get_auto_input());
+                       set_track_monitor_input_status (!config.get_auto_input());
                }
 
        } else if (p == "punch-in") {
@@ -3737,6 +4019,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) {
@@ -3754,10 +4040,6 @@ Session::config_changed (std::string p, bool ours)
 
                _mmc->enable_send (Config->get_send_mmc ());
 
-       } else if (p == "midi-feedback") {
-
-               session_midi_feedback = Config->get_midi_feedback();
-
        } else if (p == "jack-time-master") {
 
                engine().reset_timebase ();
@@ -3809,7 +4091,7 @@ Session::config_changed (std::string p, bool ours)
        } else if (p == "solo-control-is-listen-control") {
                solo_control_mode_changed ();
        } else if (p == "solo-mute-gain") {
-               _solo_cut_control->Changed();
+               _solo_cut_control->Changed (true, Controllable::NoGroup);
        } else if (p == "timecode-offset" || p == "timecode-offset-negative") {
                last_timecode_valid = false;
        } else if (p == "playback-buffer-seconds") {
@@ -3836,30 +4118,30 @@ Session::set_history_depth (uint32_t d)
 int
 Session::load_diskstreams_2X (XMLNode const & node, int)
 {
-        XMLNodeList          clist;
-        XMLNodeConstIterator citer;
+       XMLNodeList          clist;
+       XMLNodeConstIterator citer;
 
-        clist = node.children();
+       clist = node.children();
 
-        for (citer = clist.begin(); citer != clist.end(); ++citer) {
+       for (citer = clist.begin(); citer != clist.end(); ++citer) {
 
-                try {
-                        /* diskstreams added automatically by DiskstreamCreated handler */
-                        if ((*citer)->name() == "AudioDiskstream" || (*citer)->name() == "DiskStream") {
+               try {
+                       /* diskstreams added automatically by DiskstreamCreated handler */
+                       if ((*citer)->name() == "AudioDiskstream" || (*citer)->name() == "DiskStream") {
                                boost::shared_ptr<AudioDiskstream> dsp (new AudioDiskstream (*this, **citer));
                                _diskstreams_2X.push_back (dsp);
-                        } else {
-                                error << _("Session: unknown diskstream type in XML") << endmsg;
-                        }
-                }
-
-                catch (failed_constructor& err) {
-                        error << _("Session: could not load diskstream via XML state") << endmsg;
-                        return -1;
-                }
-        }
-
-        return 0;
+                       } else {
+                               error << _("Session: unknown diskstream type in XML") << endmsg;
+                       }
+               }
+
+               catch (failed_constructor& err) {
+                       error << _("Session: could not load diskstream via XML state") << endmsg;
+                       return -1;
+               }
+       }
+
+       return 0;
 }
 
 /** Connect things to the MMC object */
@@ -3906,16 +4188,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
@@ -3928,7 +4209,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);
 }
 
@@ -3954,7 +4235,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
        }
@@ -4084,12 +4365,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;
                }
@@ -4157,66 +4438,82 @@ Session::rename (const std::string& new_name)
 }
 
 int
-Session::get_session_info_from_path (XMLTree& tree, const string& xmlpath)
+Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFormat& data_format, std::string& program_version)
 {
+       bool found_sr = false;
+       bool found_data_format = false;
+       program_version = "";
+
        if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
                return -1;
-        }
+       }
 
-       if (!tree.read (xmlpath)) {
+       xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
+       if (ctxt == NULL) {
                return -1;
        }
+       xmlDocPtr doc = xmlCtxtReadFile (ctxt, xmlpath.c_str(), NULL, XML_PARSE_HUGE);
 
-       return 0;
-}
-
-int
-Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFormat& data_format)
-{
-       XMLTree tree;
-       bool found_sr = false;
-       bool found_data_format = false;
-
-       if (get_session_info_from_path (tree, xmlpath)) {
+       if (doc == NULL) {
+               xmlFreeParserCtxt(ctxt);
                return -1;
        }
 
-       /* sample rate */
+       xmlNodePtr node = xmlDocGetRootElement(doc);
 
-       const XMLProperty* prop;
-       if ((prop = tree.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 (tree.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) {
-                               const XMLNode* option = *oc;
-                               const XMLProperty* name = option->property("name");
-
-                               if (!name) {
-                                       continue;
-                               }
+       /* sample rate */
 
-                               if (name->value() == "native-file-data-format") {
-                                       const XMLProperty* value = option->property ("value");
-                                       if (value) {
-                                               SampleFormat fmt = (SampleFormat) string_2_enum (option->property ("value")->value(), fmt);
-                                               data_format = fmt;
-                                               found_data_format = true;
-                                               break;
-                                       }
-                               }
-                       }
-               }
-               if (found_data_format) {
-                       break;
-               }
-       }
+       xmlAttrPtr attr;
+       for (attr = node->properties; attr; attr = attr->next) {
+               if (!strcmp ((const char*)attr->name, "sample-rate") && attr->children) {
+                       sample_rate = atoi ((char*)attr->children->content);
+                       found_sr = true;
+               }
+       }
+
+       node = node->children;
+       while (node != NULL) {
+                if (!strcmp((const char*) node->name, "ProgramVersion")) {
+                        xmlChar* val = xmlGetProp (node, (const xmlChar*)"modified-with");
+                        if (val) {
+                                program_version = string ((const char*)val);
+                                size_t sep = program_version.find_first_of("-");
+                                if (sep != string::npos) {
+                                        program_version = program_version.substr (0, sep);
+                                }
+                        }
+                        xmlFree (val);
+                }
+                if (strcmp((const char*) node->name, "Config")) {
+                        node = node->next;
+                        continue;
+                }
+                for (node = node->children; node; node = node->next) {
+                        xmlChar* pv = xmlGetProp (node, (const xmlChar*)"name");
+                        if (pv && !strcmp ((const char*)pv, "native-file-data-format")) {
+                                xmlFree (pv);
+                                xmlChar* val = xmlGetProp (node, (const xmlChar*)"value");
+                                if (val) {
+                                        SampleFormat fmt = (SampleFormat) string_2_enum (string ((const char*)val), fmt);
+                                        data_format = fmt;
+                                        found_data_format = true;
+                                }
+                                xmlFree (val);
+                                break;
+                        }
+                        xmlFree (pv);
+                }
+                break;
+       }
+
+       xmlFreeParserCtxt(ctxt);
+       xmlFreeDoc (doc);
 
        return !(found_sr && found_data_format); // zero if they are both found
 }
@@ -4235,7 +4532,7 @@ Session::get_snapshot_from_instant (const std::string& session_dir)
                return "";
        }
 
-       const XMLProperty* prop;
+       XMLProperty const * prop;
        XMLNode *last_used_snapshot = tree.root()->child("LastUsedSnapshot");
        if (last_used_snapshot && (prop = last_used_snapshot->property ("name")) != 0) {
                return prop->value();
@@ -4657,7 +4954,6 @@ Session::save_as (SaveAs& saveas)
                        }
                }
 
-
                _path = to_dir;
                set_snapshot_name (saveas.new_name);
                _name = saveas.new_name;
@@ -4683,7 +4979,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) {
@@ -4698,6 +4993,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;
@@ -4731,6 +5030,19 @@ Session::save_as (SaveAs& saveas)
                         */
                        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.
                        */
@@ -4776,3 +5088,340 @@ Session::save_as (SaveAs& saveas)
 
        return 0;
 }
+
+static void set_progress (Progress* p, size_t n, size_t t)
+{
+       p->set_progress (float (n) / float(t));
+}
+
+int
+Session::archive_session (const std::string& dest,
+                          const std::string& name,
+                          ArchiveEncode compress_audio,
+                          bool only_used_sources,
+                          Progress* progress)
+{
+       if (dest.empty () || name.empty ()) {
+               return -1;
+       }
+
+       /* save current values */
+       bool was_dirty = dirty ();
+       string old_path = _path;
+       string old_name = _name;
+       string old_snapshot = _current_snapshot_name;
+       string old_sd = _session_dir->root_path();
+       string old_config_search_path[DataType::num_types];
+       old_config_search_path[DataType::AUDIO] = config.get_audio_search_path ();
+       old_config_search_path[DataType::MIDI]  = config.get_midi_search_path ();
+
+       /* ensure that session-path is included in search-path */
+       bool ok = false;
+       for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+               if ((*sd).path == old_path) {
+                       ok = true;
+               }
+       }
+       if (!ok) {
+               return -1;
+       }
+
+       /* create temporary dir to save session to */
+#ifdef PLATFORM_WINDOWS
+       char tmp[256] = "C:\\TEMP\\";
+       GetTempPath (sizeof (tmp), tmp);
+#else
+       char const* tmp = getenv("TMPDIR");
+       if (!tmp) {
+               tmp = "/tmp/";
+       }
+#endif
+       if ((strlen (tmp) + 21) > 1024) {
+               return -1;
+       }
+
+       char tmptpl[1024];
+       strcpy (tmptpl, tmp);
+       strcat (tmptpl, "ardourarchive-XXXXXX");
+       char*  tmpdir = g_mkdtemp (tmptpl);
+
+       if (!tmpdir) {
+               return -1;
+       }
+
+       std::string to_dir = std::string (tmpdir);
+
+       /* switch session directory temporarily */
+       (*_session_dir) = to_dir;
+
+       if (!_session_dir->create()) {
+               (*_session_dir) = old_sd;
+               remove_directory (to_dir);
+               return -1;
+       }
+
+       /* prepare archive */
+       string archive = Glib::build_filename (dest, name + ".tar.xz");
+
+       PBD::ScopedConnectionList progress_connection;
+       PBD::FileArchive ar (archive);
+       if (progress) {
+               ar.progress.connect_same_thread (progress_connection, boost::bind (&set_progress, progress, _1, _2));
+       }
+
+       /* collect files to archive */
+       std::map<string,string> filemap;
+
+       vector<string> do_not_copy_extensions;
+       do_not_copy_extensions.push_back (statefile_suffix);
+       do_not_copy_extensions.push_back (pending_suffix);
+       do_not_copy_extensions.push_back (backup_suffix);
+       do_not_copy_extensions.push_back (temp_suffix);
+       do_not_copy_extensions.push_back (history_suffix);
+
+       vector<string> blacklist_dirs;
+       blacklist_dirs.push_back (string (peak_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (analysis_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (dead_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (export_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (externals_dir_name) + G_DIR_SEPARATOR);
+       blacklist_dirs.push_back (string (plugins_dir_name) + G_DIR_SEPARATOR);
+
+       std::map<boost::shared_ptr<AudioFileSource>, std::string> orig_sources;
+       std::map<boost::shared_ptr<AudioFileSource>, float> orig_gain;
+
+       set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
+       if (only_used_sources) {
+               playlists->sync_all_regions_with_regions ();
+               playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot), false);
+       }
+
+       // collect audio sources for this session, calc total size for encoding
+       // add option to only include *used* sources (see Session::cleanup_sources)
+       size_t total_size = 0;
+       {
+               Glib::Threads::Mutex::Lock lm (source_lock);
+               for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+                       boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (i->second);
+                       if (!afs || afs->readable_length () == 0) {
+                               continue;
+                       }
+
+                       if (only_used_sources) {
+                               if (!afs->used()) {
+                                       continue;
+                               }
+                               if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
+                                       continue;
+                               }
+                       }
+
+                       std::string from = afs->path();
+
+                       if (compress_audio != NO_ENCODE) {
+                               total_size += afs->readable_length ();
+                       } else {
+                               if (afs->within_session()) {
+                                       filemap[from] = make_new_media_path (from, name, name);
+                               } else {
+                                       filemap[from] = make_new_media_path (from, name, name);
+                                       remove_dir_from_search_path (Glib::path_get_dirname (from), DataType::AUDIO);
+                               }
+                       }
+               }
+       }
+
+       /* encode audio */
+       if (compress_audio != NO_ENCODE) {
+               if (progress) {
+                       progress->set_progress (2); // set to "encoding"
+                       progress->set_progress (0);
+               }
+
+               Glib::Threads::Mutex::Lock lm (source_lock);
+               for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+                       boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (i->second);
+                       if (!afs || afs->readable_length () == 0) {
+                               continue;
+                       }
+
+                       if (only_used_sources) {
+                               if (!afs->used()) {
+                                       continue;
+                               }
+                               if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
+                                       continue;
+                               }
+                       }
+
+                       orig_sources[afs] = afs->path();
+                       orig_gain[afs]    = afs->gain();
+
+                       std::string new_path = make_new_media_path (afs->path (), to_dir, name);
+                       new_path = Glib::build_filename (Glib::path_get_dirname (new_path), PBD::basename_nosuffix (new_path) + ".flac");
+                       g_mkdir_with_parents (Glib::path_get_dirname (new_path).c_str (), 0755);
+
+                       if (progress) {
+                               progress->descend ((float)afs->readable_length () / total_size);
+                       }
+
+                       try {
+                               SndFileSource* ns = new SndFileSource (*this, *(afs.get()), new_path, compress_audio == FLAC_16BIT, progress);
+                               afs->replace_file (new_path);
+                               afs->set_gain (ns->gain(), true);
+                               delete ns;
+                       } catch (...) {
+                               cerr << "failed to encode " << afs->path() << " to " << new_path << "\n";
+                       }
+
+                       if (progress) {
+                               progress->ascend ();
+                       }
+               }
+       }
+
+       if (progress) {
+               progress->set_progress (-1); // set to "archiving"
+               progress->set_progress (0);
+       }
+
+       /* index files relevant for this session */
+       for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+               vector<string> files;
+
+               size_t prefix_len = (*sd).path.size();
+               if (prefix_len > 0 && (*sd).path.at (prefix_len - 1) != G_DIR_SEPARATOR) {
+                       ++prefix_len;
+               }
+
+               find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
+
+               static const std::string audiofile_dir_string = string (sound_dir_name) + G_DIR_SEPARATOR;
+               static const std::string videofile_dir_string = string (video_dir_name) + G_DIR_SEPARATOR;
+               static const std::string midifile_dir_string  = string (midi_dir_name)  + G_DIR_SEPARATOR;
+
+               for (vector<string>::const_iterator i = files.begin (); i != files.end (); ++i) {
+                       std::string from = *i;
+
+#ifdef __APPLE__
+                       string filename = Glib::path_get_basename (from);
+                       std::transform (filename.begin(), filename.end(), filename.begin(), ::toupper);
+                       if (filename == ".DS_STORE") {
+                               continue;
+                       }
+#endif
+
+                       if (from.find (audiofile_dir_string) != string::npos) {
+                               ; // handled above
+                       } else if (from.find (midifile_dir_string) != string::npos) {
+                               filemap[from] = make_new_media_path (from, name, name);
+                       } else if (from.find (videofile_dir_string) != string::npos) {
+                               filemap[from] = make_new_media_path (from, name, name);
+                       } else {
+                               bool do_copy = true;
+                               for (vector<string>::iterator v = blacklist_dirs.begin(); v != blacklist_dirs.end(); ++v) {
+                                       if (from.find (*v) != string::npos) {
+                                               do_copy = false;
+                                               break;
+                                       }
+                               }
+                               for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
+                                       if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
+                                               do_copy = false;
+                                               break;
+                                       }
+                               }
+
+                               if (do_copy) {
+                                       filemap[from] = name + G_DIR_SEPARATOR + from.substr (prefix_len);
+                               }
+                       }
+               }
+       }
+
+       /* write session file */
+       _path = to_dir;
+       g_mkdir_with_parents (externals_dir ().c_str (), 0755);
+#ifdef LV2_SUPPORT
+       PBD::Unwinder<bool> uw (LV2Plugin::force_state_save, true);
+#endif
+       save_state (name);
+       save_default_options ();
+
+       size_t prefix_len = _path.size();
+       if (prefix_len > 0 && _path.at (prefix_len - 1) != G_DIR_SEPARATOR) {
+               ++prefix_len;
+       }
+
+       /* collect session-state files */
+       vector<string> files;
+       do_not_copy_extensions.clear ();
+       do_not_copy_extensions.push_back (history_suffix);
+
+       blacklist_dirs.clear ();
+       blacklist_dirs.push_back (string (externals_dir_name) + G_DIR_SEPARATOR);
+
+       find_files_matching_filter (files, to_dir, accept_all_files, 0, false, true, true);
+       for (vector<string>::const_iterator i = files.begin (); i != files.end (); ++i) {
+               std::string from = *i;
+               bool do_copy = true;
+               for (vector<string>::iterator v = blacklist_dirs.begin(); v != blacklist_dirs.end(); ++v) {
+                       if (from.find (*v) != string::npos) {
+                               do_copy = false;
+                               break;
+                       }
+               }
+               for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
+                       if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
+                               do_copy = false;
+                               break;
+                       }
+               }
+               if (do_copy) {
+                       filemap[from] = name + G_DIR_SEPARATOR + from.substr (prefix_len);
+               }
+       }
+
+       /* restore original values */
+       _path = old_path;
+       _name = old_name;
+       set_snapshot_name (old_snapshot);
+       (*_session_dir) = old_sd;
+       if (was_dirty) {
+               set_dirty ();
+       }
+       config.set_audio_search_path (old_config_search_path[DataType::AUDIO]);
+       config.set_midi_search_path (old_config_search_path[DataType::MIDI]);
+
+       for (std::map<boost::shared_ptr<AudioFileSource>, std::string>::iterator i = orig_sources.begin (); i != orig_sources.end (); ++i) {
+               i->first->replace_file (i->second);
+       }
+       for (std::map<boost::shared_ptr<AudioFileSource>, float>::iterator i = orig_gain.begin (); i != orig_gain.end (); ++i) {
+               i->first->set_gain (i->second, true);
+       }
+
+       int rv = ar.create (filemap);
+       remove_directory (to_dir);
+
+       return rv;
+}
+
+void
+Session::undo (uint32_t n)
+{
+       if (actively_recording()) {
+               return;
+       }
+
+       _history.undo (n);
+}
+
+void
+Session::redo (uint32_t n)
+{
+       if (actively_recording()) {
+               return;
+       }
+
+       _history.redo (n);
+}