Don't use VST ->user points, prefer host-reserved ptr
[ardour.git] / libs / ardour / session_state.cc
index 4ad1f91f4ab716fdcfbba31ce189c3d27ee7be29..5e1525dbed5bb198b68fbee243f9d000d6b26e8d 100644 (file)
@@ -50,6 +50,7 @@
 
 #include <glib.h>
 #include "pbd/gstdio_compat.h"
+#include "pbd/locale_guard.h"
 
 #include <glibmm.h>
 #include <glibmm/threads.h>
 #include "ardour/revision.h"
 #include "ardour/route_group.h"
 #include "ardour/send.h"
+#include "ardour/selection.h"
 #include "ardour/session.h"
 #include "ardour/session_directory.h"
 #include "ardour/session_metadata.h"
 #include "ardour/session_playlists.h"
 #include "ardour/session_state_utils.h"
 #include "ardour/silentfilesource.h"
+#include "ardour/smf_source.h"
 #include "ardour/sndfilesource.h"
 #include "ardour/source_factory.h"
 #include "ardour/speakers.h"
@@ -262,7 +265,15 @@ Session::post_engine_init ()
                _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));
+       } catch (std::exception const & e) {
+               error << _("Unexpected exception during session setup: ") << e.what() << endmsg;
+               return -2;
+       } catch (...) {
+               error << _("Unknown exception during session setup") << endmsg;
+               return -3;
+       }
 
+       try {
                /* MidiClock requires a tempo map */
 
                delete midi_clock;
@@ -285,7 +296,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;
+                               return -4;
                        }
                } else {
                        // set_state() will call setup_raid_path(), but if it's a new session we need
@@ -348,15 +359,14 @@ Session::post_engine_init ()
                _locations->changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this));
 
        } catch (AudioEngine::PortRegistrationFailure& err) {
-               /* handle this one in a different way than all others, so that its clear what happened */
                error << err.what() << endmsg;
-               return -1;
+               return -5;
        } catch (std::exception const & e) {
                error << _("Unexpected exception during session setup: ") << e.what() << endmsg;
-               return -1;
+               return -6;
        } catch (...) {
                error << _("Unknown exception during session setup") << endmsg;
-               return -1;
+               return -7;
        }
 
        BootMessage (_("Reset Remote Controls"));
@@ -798,10 +808,10 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
        }
        _save_queued = false;
 
-       if (!_engine.connected ()) {
-               error << string_compose (_("the %1 audio engine is not connected and state saving would lose all I/O connections. Session not saved"), PROGRAM_NAME)
-                     << endmsg;
-               return 1;
+       snapshot_t fork_state = NormalSave;
+       if (!snapshot_name.empty() && snapshot_name != _current_snapshot_name && !template_only && !pending) {
+               /* snapshot, close midi */
+               fork_state = switch_to_snapshot ? SwitchToSnapshot : SnapshotKeep;
        }
 
 #ifndef NDEBUG
@@ -830,7 +840,7 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
                mark_as_clean = false;
                tree.set_root (&get_template());
        } else {
-               tree.set_root (&get_state());
+               tree.set_root (&state (true, fork_state));
        }
 
        if (snapshot_name.empty()) {
@@ -1022,7 +1032,6 @@ Session::load_state (string snapshot_name)
 int
 Session::load_options (const XMLNode& node)
 {
-       LocaleGuard lg;
        config.set_variables (node);
        return 0;
 }
@@ -1126,8 +1135,19 @@ Session::export_track_state (boost::shared_ptr<RouteList> rl, const string& path
        return tree.write (sn.c_str());
 }
 
+namespace
+{
+struct route_id_compare {
+       bool
+       operator() (const boost::shared_ptr<Route>& r1, const boost::shared_ptr<Route>& r2)
+       {
+               return r1->id () < r2->id ();
+       }
+};
+} // anon namespace
+
 XMLNode&
-Session::state (bool full_state)
+Session::state (bool full_state, snapshot_t snapshot_type)
 {
        LocaleGuard lg;
        XMLNode* node = new XMLNode("Session");
@@ -1176,10 +1196,9 @@ Session::state (bool full_state)
                        child = node->add_child ("Path");
                        child->add_content (p);
                }
+               node->set_property ("end-is-free", _session_range_end_is_free);
        }
 
-       node->set_property ("end-is-free", _session_range_end_is_free);
-
        /* save the ID counter */
 
        node->set_property ("id-counter", ID::counter());
@@ -1227,19 +1246,80 @@ Session::state (bool full_state)
                         * about non-destructive file sources that are empty
                         * and unused by any regions.
                         */
-
                        boost::shared_ptr<FileSource> fs;
 
-                       if ((fs = boost::dynamic_pointer_cast<FileSource> (siter->second)) != 0) {
+                       if ((fs = boost::dynamic_pointer_cast<FileSource> (siter->second)) == 0) {
+                               continue;
+                       }
+
+                       if (!fs->destructive()) {
+                               if (fs->empty() && !fs->used()) {
+                                       continue;
+                               }
+                       }
 
-                               if (!fs->destructive()) {
-                                       if (fs->empty() && !fs->used()) {
+                       if (snapshot_type != NormalSave && fs->within_session ()) {
+                               /* copy MIDI sources to new file
+                                *
+                                * We cannot replace the midi-source and MidiRegion::clobber_sources,
+                                * because the GUI (midi_region) has a direct pointer to the midi-model
+                                * of the source, as does UndoTransaction.
+                                *
+                                * On the upside, .mid files are not kept open. The file is only open
+                                * when reading the model initially and when flushing the model to disk:
+                                * source->session_saved () or export.
+                                *
+                                * We can change the _path of the existing source under the hood, keeping
+                                * all IDs, references and pointers intact.
+                                * */
+                               boost::shared_ptr<SMFSource> ms;
+                               if ((ms = boost::dynamic_pointer_cast<SMFSource> (siter->second)) != 0) {
+                                       const std::string ancestor_name = ms->ancestor_name();
+                                       const std::string base          = PBD::basename_nosuffix(ancestor_name);
+                                       const string path               = new_midi_source_path (base, false);
+
+                                       /* use SMF-API to clone data (use the midi_model, not data on disk) */
+                                       boost::shared_ptr<SMFSource> newsrc (new SMFSource (*this, path, SndFileSource::default_writable_flags));
+                                       Source::Lock lm (ms->mutex());
+
+                                       // TODO special-case empty, removable() files: just create a new removable.
+                                       // (load + write flushes the model and creates the file)
+                                       if (!ms->model()) {
+                                               ms->load_model (lm);
+                                       }
+                                       if (ms->write_to (lm, newsrc, Evoral::MinBeats, Evoral::MaxBeats)) {
+                                               error << string_compose (_("Session-Save: Failed to copy MIDI Source '%1' for snapshot"), ancestor_name) << endmsg;
+                                       } else {
+                                               if (snapshot_type == SnapshotKeep) {
+                                                       /* keep working on current session.
+                                                        *
+                                                        * Save snapshot-state with the original filename.
+                                                        * Switch to use new path for future saves of the main session.
+                                                        */
+                                                       child->add_child_nocopy (ms->get_state());
+                                               }
+
+                                               /* swap file-paths.
+                                                * ~SMFSource  unlinks removable() files.
+                                                */
+                                               std::string npath (ms->path ());
+                                               ms->replace_file (newsrc->path ());
+                                               newsrc->replace_file (npath);
+
+                                               if (snapshot_type == SwitchToSnapshot) {
+                                                       /* save and switch to snapshot.
+                                                        *
+                                                        * Leave the old file in place (as is).
+                                                        * Snapshot uses new source directly
+                                                        */
+                                                       child->add_child_nocopy (ms->get_state());
+                                               }
                                                continue;
                                        }
                                }
-
-                               child->add_child_nocopy (siter->second->get_state());
                        }
+
+                       child->add_child_nocopy (siter->second->get_state());
                }
        }
 
@@ -1276,6 +1356,8 @@ Session::state (bool full_state)
 
        if (full_state) {
 
+               node->add_child_nocopy (_selection->get_state());
+
                if (_locations) {
                        node->add_child_nocopy (_locations->get_state());
                }
@@ -1326,17 +1408,11 @@ Session::state (bool full_state)
        {
                boost::shared_ptr<RouteList> r = routes.reader ();
 
-               RoutePublicOrderSorter cmp;
-               RouteList public_order (*r);
-               public_order.sort (cmp);
-
-               /* the sort should have put the monitor out first */
-
-               if (_monitor_out) {
-                       assert (_monitor_out == public_order.front());
-               }
+               route_id_compare cmp;
+               RouteList xml_node_order (*r);
+               xml_node_order.sort (cmp);
 
-               for (RouteList::iterator i = public_order.begin(); i != public_order.end(); ++i) {
+               for (RouteList::const_iterator i = xml_node_order.begin(); i != xml_node_order.end(); ++i) {
                        if (!(*i)->is_auditioner()) {
                                if (full_state) {
                                        child->add_child_nocopy ((*i)->get_state());
@@ -1645,6 +1721,10 @@ Session::set_state (const XMLNode& node, int version)
                }
        }
 
+       if ((child = find_named_node (node, X_("Selection")))) {
+               _selection->set_state (*child, version);
+       }
+
        update_route_record_state ();
 
        /* here beginneth the second phase ... */
@@ -3579,6 +3659,12 @@ Session::controllable_by_id (const PBD::ID& id)
        return boost::shared_ptr<Controllable>();
 }
 
+boost::shared_ptr<AutomationControl>
+Session::automation_control_by_id (const PBD::ID& id)
+{
+       return boost::dynamic_pointer_cast<AutomationControl> (controllable_by_id (id));
+}
+
 boost::shared_ptr<Controllable>
 Session::controllable_by_descriptor (const ControllableDescriptor& desc)
 {
@@ -3823,13 +3909,21 @@ Session::restore_history (string snapshot_name)
 
                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();
@@ -5018,6 +5112,8 @@ Session::save_as (SaveAs& saveas)
                        session_dirs.push_back (sp);
                        refresh_disk_space ();
 
+                       _writable = exists_and_writable (_path);
+
                        /* ensure that all existing tracks reset their current capture source paths
                         */
                        reset_write_sources (true, true);