NO-OP; whitespace
[ardour.git] / libs / ardour / session_state.cc
index ba0474f741d1757fc1691bde04d332f70bdaf806..5e63e0c35701e8368574f534deb9d7fa71a4eacd 100644 (file)
@@ -92,7 +92,9 @@
 #include "ardour/filename_extensions.h"
 #include "ardour/graph.h"
 #include "ardour/location.h"
+#ifdef LV2_SUPPORT
 #include "ardour/lv2_plugin.h"
+#endif
 #include "ardour/midi_model.h"
 #include "ardour/midi_patch_manager.h"
 #include "ardour/midi_region.h"
@@ -258,7 +260,7 @@ Session::post_engine_init ()
                delete _tempo_map;
                _tempo_map = new TempoMap (_current_frame_rate);
                _tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
-               _tempo_map->MetricPositionChanged.connect_same_thread (*this, boost::bind (&Session::gui_tempo_map_changed, this));
+               _tempo_map->MetricPositionChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
 
                /* MidiClock requires a tempo map */
 
@@ -270,6 +272,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 ();
@@ -633,7 +636,7 @@ Session::create (const string& session_template, BusProfile* bus_profile)
 
                /* 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);
        }
@@ -1050,6 +1053,80 @@ 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)
 {
@@ -1217,9 +1294,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();
@@ -1233,6 +1311,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");
@@ -2102,10 +2189,11 @@ 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();
 
@@ -2116,7 +2204,7 @@ Session::load_sources (const XMLNode& node)
                int old_mode = 0;
 #endif
 
-          retry:
+retry:
                try {
 #ifdef PLATFORM_WINDOWS
                        // do not show "insert media" popups (files embedded from removable media).
@@ -2134,72 +2222,71 @@ Session::load_sources (const XMLNode& node)
                        SetErrorMode(old_mode);
 #endif
 
-                        int user_choice;
+                       int user_choice;
 
                        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;
+                                               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, so try again */
+                                       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;
-                        }
+                       }
                }
        }
 
@@ -2617,6 +2704,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)
 {
@@ -3407,13 +3513,11 @@ Session::set_dirty ()
 
        _state_of_the_state = StateOfTheState (_state_of_the_state | Dirty);
 
-
        if (!was_dirty) {
                DirtyChanged(); /* EMIT SIGNAL */
        }
 }
 
-
 void
 Session::set_clean ()
 {
@@ -3421,7 +3525,6 @@ Session::set_clean ()
 
        _state_of_the_state = Clean;
 
-
        if (was_dirty) {
                DirtyChanged(); /* EMIT SIGNAL */
        }
@@ -3636,6 +3739,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);
 }
 
@@ -3719,7 +3827,7 @@ 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 ();
@@ -3806,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") {
@@ -3926,10 +4036,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 ();
@@ -4329,68 +4435,82 @@ Session::rename (const std::string& new_name)
 }
 
 int
-Session::get_session_info_from_path (XMLTree& tree, const string& xmlpath)
+Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFormat& data_format, std::string& program_version)
 {
+       bool found_sr = false;
+       bool found_data_format = false;
+       program_version = "";
+
        if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
                return -1;
-        }
+       }
 
-       if (!tree.read (xmlpath)) {
+       xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
+       if (ctxt == NULL) {
                return -1;
        }
+       xmlDocPtr doc = xmlCtxtReadFile (ctxt, xmlpath.c_str(), NULL, XML_PARSE_HUGE);
 
-       return 0;
-}
-
-int
-Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFormat& data_format)
-{
-       XMLTree tree;
-       bool found_sr = false;
-       bool found_data_format = false;
-
-       if (get_session_info_from_path (tree, xmlpath)) {
+       if (doc == NULL) {
+               xmlFreeParserCtxt(ctxt);
                return -1;
        }
 
-       /* sample rate */
-
-       XMLProperty const * prop;
-       XMLNode const * root (tree.root());
+       xmlNodePtr node = xmlDocGetRootElement(doc);
 
-       if ((prop = root->property (X_("sample-rate"))) != 0) {
-               sample_rate = atoi (prop->value());
-               found_sr = true;
+       if (node == NULL) {
+               xmlFreeParserCtxt(ctxt);
+               xmlFreeDoc (doc);
+               return -1;
        }
 
-       const XMLNodeList& children (root->children());
-       for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
-               const XMLNode* child = *c;
-               if (child->name() == "Config") {
-                       const XMLNodeList& options (child->children());
-                       for (XMLNodeList::const_iterator oc = options.begin(); oc != options.end(); ++oc) {
-                               XMLNode const * option = *oc;
-                               XMLProperty const * name = option->property("name");
-
-                               if (!name) {
-                                       continue;
-                               }
+       /* sample rate */
 
-                               if (name->value() == "native-file-data-format") {
-                                       XMLProperty const * value = option->property ("value");
-                                       if (value) {
-                                               SampleFormat fmt = (SampleFormat) string_2_enum (option->property ("value")->value(), fmt);
-                                               data_format = fmt;
-                                               found_data_format = true;
-                                               break;
-                                       }
-                               }
-                       }
-               }
-               if (found_data_format) {
-                       break;
-               }
-       }
+       xmlAttrPtr attr;
+       for (attr = node->properties; attr; attr = attr->next) {
+               if (!strcmp ((const char*)attr->name, "sample-rate") && attr->children) {
+                       sample_rate = atoi ((char*)attr->children->content);
+                       found_sr = true;
+               }
+       }
+
+       node = node->children;
+       while (node != NULL) {
+                if (!strcmp((const char*) node->name, "ProgramVersion")) {
+                        xmlChar* val = xmlGetProp (node, (const xmlChar*)"modified-with");
+                        if (val) {
+                                program_version = string ((const char*)val);
+                                size_t sep = program_version.find_first_of("-");
+                                if (sep != string::npos) {
+                                        program_version = program_version.substr (0, sep);
+                                }
+                        }
+                        xmlFree (val);
+                }
+                if (strcmp((const char*) node->name, "Config")) {
+                        node = node->next;
+                        continue;
+                }
+                for (node = node->children; node; node = node->next) {
+                        xmlChar* pv = xmlGetProp (node, (const xmlChar*)"name");
+                        if (pv && !strcmp ((const char*)pv, "native-file-data-format")) {
+                                xmlFree (pv);
+                                xmlChar* val = xmlGetProp (node, (const xmlChar*)"value");
+                                if (val) {
+                                        SampleFormat fmt = (SampleFormat) string_2_enum (string ((const char*)val), fmt);
+                                        data_format = fmt;
+                                        found_data_format = true;
+                                }
+                                xmlFree (val);
+                                break;
+                        }
+                        xmlFree (pv);
+                }
+                break;
+       }
+
+       xmlFreeParserCtxt(ctxt);
+       xmlFreeDoc (doc);
 
        return !(found_sr && found_data_format); // zero if they are both found
 }
@@ -4831,7 +4951,6 @@ Session::save_as (SaveAs& saveas)
                        }
                }
 
-
                _path = to_dir;
                set_snapshot_name (saveas.new_name);
                _name = saveas.new_name;
@@ -4857,7 +4976,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) {
@@ -4872,6 +4990,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;
@@ -4905,6 +5027,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.
                        */
@@ -5050,6 +5185,7 @@ Session::archive_session (const std::string& dest,
        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) {
@@ -5116,6 +5252,7 @@ Session::archive_session (const std::string& dest,
                        }
 
                        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");
@@ -5128,6 +5265,7 @@ Session::archive_session (const std::string& dest,
                        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";
@@ -5201,8 +5339,9 @@ Session::archive_session (const std::string& dest,
        /* 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 ();
 
@@ -5254,6 +5393,9 @@ Session::archive_session (const std::string& dest,
        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);