Emit Session::MuteChanged() if a mutes route is removed.
[ardour.git] / libs / ardour / session_state.cc
index fbfefaec7e2ecfcb154ce571f5867c901bb5d1fd..edca09ff144511832a3c8565f8fce0b548c3dbb7 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"
@@ -231,7 +233,7 @@ 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 ();
@@ -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 */
 
@@ -634,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);
        }
@@ -1051,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)
 {
@@ -1218,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();
@@ -1234,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");
@@ -2103,27 +2189,32 @@ 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) {
 #ifdef PLATFORM_WINDOWS
                int old_mode = 0;
 #endif
 
-          retry:
+               XMLNode srcnode (**niter);
+               bool try_replace_abspath = true;
+
+retry:
                try {
 #ifdef PLATFORM_WINDOWS
                        // do not show "insert media" popups (files embedded from removable media).
                        old_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
 #endif
-                       if ((source = XMLSourceFactory (**niter)) == 0) {
+                       if ((source = XMLSourceFactory (srcnode)) == 0) {
                                error << _("Session: cannot create Source from XML description.") << endmsg;
                        }
 #ifdef PLATFORM_WINDOWS
@@ -2135,72 +2226,98 @@ Session::load_sources (const XMLNode& node)
                        SetErrorMode(old_mode);
 #endif
 
-                        int user_choice;
+                       /* 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.add_property ("origin", newpath);
+                                               try_replace_abspath = false;
+                                               goto retry;
+                                       }
+                               }
+                       }
+
+                       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.add_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;
-                        }
+                       }
                }
        }
 
@@ -2618,6 +2735,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)
 {
@@ -3398,23 +3534,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 ()
 {
@@ -3422,7 +3555,6 @@ Session::set_clean ()
 
        _state_of_the_state = Clean;
 
-
        if (was_dirty) {
                DirtyChanged(); /* EMIT SIGNAL */
        }
@@ -3812,11 +3944,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") {
@@ -3915,6 +4049,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) {
@@ -3932,10 +4070,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 ();
@@ -4335,68 +4469,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
 }
@@ -4837,7 +4985,6 @@ Session::save_as (SaveAs& saveas)
                        }
                }
 
-
                _path = to_dir;
                set_snapshot_name (saveas.new_name);
                _name = saveas.new_name;
@@ -4863,7 +5010,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) {
@@ -4878,6 +5024,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;
@@ -4911,6 +5061,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.
                        */
@@ -5056,6 +5219,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) {
@@ -5122,6 +5286,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");
@@ -5134,6 +5299,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";
@@ -5207,8 +5373,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 ();
 
@@ -5260,6 +5427,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);