X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=libs%2Fardour%2Fsession_state.cc;h=5e1525dbed5bb198b68fbee243f9d000d6b26e8d;hb=c4fcb12d128857a0eaab7d2093d38fdf4cc641cc;hp=5e63e0c35701e8368574f534deb9d7fa71a4eacd;hpb=66163305317e8acf20c4a16b9709fc809d6a0ff9;p=ardour.git diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 5e63e0c357..5e1525dbed 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -50,6 +50,7 @@ #include #include "pbd/gstdio_compat.h" +#include "pbd/locale_guard.h" #include #include @@ -71,7 +72,7 @@ #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" @@ -114,18 +115,21 @@ #include "ardour/revision.h" #include "ardour/route_group.h" #include "ardour/send.h" +#include "ardour/selection.h" #include "ardour/session.h" #include "ardour/session_directory.h" #include "ardour/session_metadata.h" #include "ardour/session_playlists.h" #include "ardour/session_state_utils.h" #include "ardour/silentfilesource.h" +#include "ardour/smf_source.h" #include "ardour/sndfilesource.h" #include "ardour/source_factory.h" #include "ardour/speakers.h" #include "ardour/template_utils.h" #include "ardour/tempo.h" #include "ardour/ticker.h" +#include "ardour/types_convert.h" #include "ardour/user_bundle.h" #include "ardour/vca.h" #include "ardour/vca_manager.h" @@ -192,14 +196,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 */ @@ -233,7 +237,7 @@ Session::post_engine_init () msc->set_input_port (boost::dynamic_pointer_cast(scene_input_port())); msc->set_output_port (boost::dynamic_pointer_cast(scene_output_port())); - boost::function timer_func (boost::bind (&Session::audible_frame, this)); + boost::function timer_func (boost::bind (&Session::audible_frame, this, (bool*)(0))); boost::dynamic_pointer_cast(scene_input_port())->set_timer (timer_func); setup_midi_machine_control (); @@ -261,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; @@ -284,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 @@ -299,7 +311,7 @@ Session::post_engine_init () Config->map_parameters (ff); config.map_parameters (ft); - _butler->map_parameters (); + _butler->map_parameters (); /* Reset all panners */ @@ -347,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")); @@ -628,9 +639,9 @@ 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); @@ -643,23 +654,23 @@ Session::create (const string& session_template, BusProfile* bus_profile) _state_of_the_state = Clean; - /* set up Master Out and Monitor 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); + 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 r (new Route (*this, _("Master"), PresentationInfo::MasterOut, DataType::AUDIO)); - if (r->init ()) { - return -1; - } + // Waves Tracks: always create master bus for Tracks + if (ARDOUR::Profile->get_trx() || bus_profile->master_out_channels) { + boost::shared_ptr r (new Route (*this, _("Master"), PresentationInfo::MasterOut, DataType::AUDIO)); + if (r->init ()) { + return -1; + } - BOOST_MARK_ROUTE(r); + BOOST_MARK_ROUTE(r); - { + { Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); r->input()->ensure_io (count, false, this); r->output()->ensure_io (count, false, this); @@ -680,7 +691,7 @@ Session::create (const string& session_template, BusProfile* bus_profile) 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); @@ -690,7 +701,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 (); @@ -702,9 +713,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 @@ -797,11 +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()) { @@ -939,7 +949,7 @@ Session::load_state (string snapshot_name) /* there is pending state from a crashed capture attempt */ - boost::optional r = AskAboutPendingState(); + boost::optional r = AskAboutPendingState(); if (r.get_value_or (1)) { state_was_pending = true; } @@ -952,10 +962,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; @@ -979,22 +989,21 @@ Session::load_state (string snapshot_name) return -1; } - XMLProperty const * 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(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) { @@ -1023,7 +1032,6 @@ Session::load_state (string snapshot_name) int Session::load_options (const XMLNode& node) { - LocaleGuard lg; config.set_variables (node); return 0; } @@ -1127,30 +1135,38 @@ Session::export_track_state (boost::shared_ptr rl, const string& path return tree.write (sn.c_str()); } +namespace +{ +struct route_id_compare { + bool + operator() (const boost::shared_ptr& r1, const boost::shared_ptr& 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"); 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->add_property("created-with", created_with); + child->set_property("created-with", created_with); std::string modified_with = string_compose ("%1 %2", PROGRAM_NAME, revision); - child->add_property("modified-with", modified_with); + child->set_property("modified-with", modified_with); /* store configuration settings */ if (full_state) { - node->add_property ("name", _name); - snprintf (buf, sizeof (buf), "%" PRId64, _base_frame_rate); - node->add_property ("sample-rate", buf); + node->set_property ("name", _name); + node->set_property ("sample-rate", _base_frame_rate); if (session_dirs.size() > 1) { @@ -1180,27 +1196,22 @@ Session::state (bool full_state) child = node->add_child ("Path"); child->add_content (p); } + node->set_property ("end-is-free", _session_range_end_is_free); } - node->add_property ("end-is-free", _session_range_end_is_free ? X_("yes") : X_("no")); - /* save the ID counter */ - snprintf (buf, sizeof (buf), "%" PRIu64, ID::counter()); - node->add_property ("id-counter", buf); + node->set_property ("id-counter", ID::counter()); - snprintf (buf, sizeof (buf), "%u", name_id_counter ()); - node->add_property ("name-counter", buf); + node->set_property ("name-counter", name_id_counter ()); /* save the event ID counter */ - snprintf (buf, sizeof (buf), "%d", Evoral::event_id_counter()); - node->add_property ("event-counter", buf); + node->set_property ("event-counter", Evoral::event_id_counter()); /* save the VCA counter */ - snprintf (buf, sizeof (buf), "%" PRIu32, VCA::get_next_vca_number()); - node->add_property ("vca-counter", buf); + node->set_property ("vca-counter", VCA::get_next_vca_number()); /* various options */ @@ -1234,20 +1245,81 @@ 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 fs; - if ((fs = boost::dynamic_pointer_cast (siter->second)) != 0) { + if ((fs = boost::dynamic_pointer_cast (siter->second)) == 0) { + continue; + } - if (!fs->destructive()) { - if (fs->empty() && !fs->used()) { + if (!fs->destructive()) { + if (fs->empty() && !fs->used()) { + continue; + } + } + + if (snapshot_type != NormalSave && fs->within_session ()) { + /* copy MIDI sources to new file + * + * We cannot replace the midi-source and MidiRegion::clobber_sources, + * because the GUI (midi_region) has a direct pointer to the midi-model + * of the source, as does UndoTransaction. + * + * On the upside, .mid files are not kept open. The file is only open + * when reading the model initially and when flushing the model to disk: + * source->session_saved () or export. + * + * We can change the _path of the existing source under the hood, keeping + * all IDs, references and pointers intact. + * */ + boost::shared_ptr ms; + if ((ms = boost::dynamic_pointer_cast (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 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()); } } @@ -1255,18 +1327,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 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 r = i->second; + /* only store regions not attached to playlists */ + if (r->playlist() == 0) { if (boost::dynamic_pointer_cast(r)) { child->add_child_nocopy ((boost::dynamic_pointer_cast(r))->get_basic_state ()); } else { child->add_child_nocopy (r->get_state ()); } - } - } + } + } RegionFactory::CompoundAssociations& cassocs (RegionFactory::compound_associations()); @@ -1274,21 +1346,18 @@ 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) { + node->add_child_nocopy (_selection->get_state()); + if (_locations) { node->add_child_nocopy (_locations->get_state()); } @@ -1339,17 +1408,11 @@ Session::state (bool full_state) { boost::shared_ptr r = routes.reader (); - RoutePublicOrderSorter cmp; - RouteList public_order (*r); - public_order.sort (cmp); + route_id_compare cmp; + RouteList xml_node_order (*r); + xml_node_order.sort (cmp); - /* the sort should have put the monitor out first */ - - if (_monitor_out) { - assert (_monitor_out == public_order.front()); - } - - for (RouteList::iterator i = public_order.begin(); i != public_order.end(); ++i) { + for (RouteList::const_iterator i = xml_node_order.begin(); i != xml_node_order.end(); ++i) { if (!(*i)->is_auditioner()) { if (full_state) { child->add_child_nocopy ((*i)->get_state()); @@ -1383,7 +1446,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()); @@ -1406,7 +1469,7 @@ Session::state (bool full_state) g_free (b64); XMLNode* script_node = new XMLNode (X_("Script")); - script_node->add_property (X_("lua"), LUA_VERSION); + script_node->set_property (X_("lua"), LUA_VERSION); script_node->add_content (b64s); node->add_child_nocopy (*script_node); } @@ -1427,7 +1490,6 @@ Session::set_state (const XMLNode& node, int version) LocaleGuard lg; XMLNodeList nlist; XMLNode* child; - XMLProperty const * prop; int ret = -1; _state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave); @@ -1437,13 +1499,10 @@ 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)) { - _base_frame_rate = atoi (prop->value()); _nominal_frame_rate = _base_frame_rate; assert (AudioEngine::instance()->running ()); @@ -1457,43 +1516,36 @@ Session::set_state (const XMLNode& node, int version) created_with = "unknown"; if ((child = find_named_node (node, "ProgramVersion")) != 0) { - if ((prop = child->property (X_("created-with"))) != 0) { - created_with = prop->value (); - } + child->get_property (X_("created-with"), created_with); } setup_raid_path(_session_dir->root_path()); - if ((prop = node.property (X_("end-is-free"))) != 0) { - _session_range_end_is_free = string_is_affirmative (prop->value()); - } + node.get_property (X_("end-is-free"), _session_range_end_is_free); - if ((prop = node.property (X_("id-counter"))) != 0) { - uint64_t x; - sscanf (prop->value().c_str(), "%" PRIu64, &x); - ID::init_counter (x); + uint64_t counter; + if (node.get_property (X_("id-counter"), counter)) { + ID::init_counter (counter); } else { /* old sessions used a timebased counter, so fake - the startup ID counter based on a standard - timestamp. - */ + * the startup ID counter based on a standard + * timestamp. + */ time_t now; time (&now); ID::init_counter (now); } - if ((prop = node.property (X_("name-counter"))) != 0) { - init_name_id_counter (atoi (prop->value())); + if (node.get_property (X_("name-counter"), counter)) { + init_name_id_counter (counter); } - if ((prop = node.property (X_("event-counter"))) != 0) { - Evoral::init_event_id_counter (atoi (prop->value())); + if (node.get_property (X_("event-counter"), counter)) { + Evoral::init_event_id_counter (counter); } - if ((prop = node.property (X_("vca-counter"))) != 0) { - uint32_t x; - sscanf (prop->value().c_str(), "%" PRIu32, &x); - VCA::set_next_vca_number (x); + if (node.get_property (X_("vca-counter"), counter)) { + VCA::set_next_vca_number (counter); } else { VCA::set_next_vca_number (1); } @@ -1522,9 +1574,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; @@ -1585,8 +1637,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); } } @@ -1669,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 ... */ @@ -1680,7 +1736,7 @@ Session::set_state (const XMLNode& node, int version) state_tree = 0; return 0; - out: +out: delete state_tree; state_tree = 0; return ret; @@ -1737,11 +1793,7 @@ Session::XMLRouteFactory (const XMLNode& node, int version) XMLNode* ds_child = find_named_node (node, X_("Diskstream")); DataType type = DataType::AUDIO; - XMLProperty const * prop = node.property("default-type"); - - if (prop) { - type = DataType (prop->value()); - } + node.get_property("default-type", type); assert (type != DataType::NIL); @@ -1749,31 +1801,31 @@ Session::XMLRouteFactory (const XMLNode& node, int version) boost::shared_ptr track; - if (type == DataType::AUDIO) { - track.reset (new AudioTrack (*this, X_("toBeResetFroXML"))); - } else { - track.reset (new MidiTrack (*this, X_("toBeResetFroXML"))); - } + if (type == DataType::AUDIO) { + track.reset (new AudioTrack (*this, X_("toBeResetFroXML"))); + } else { + track.reset (new MidiTrack (*this, X_("toBeResetFroXML"))); + } - if (track->init()) { - return ret; - } + if (track->init()) { + return ret; + } - if (track->set_state (node, version)) { - return ret; - } + if (track->set_state (node, version)) { + return ret; + } - BOOST_MARK_TRACK (track); - ret = track; + BOOST_MARK_TRACK (track); + ret = track; } else { PresentationInfo::Flag flags = PresentationInfo::get_flags (node); boost::shared_ptr r (new Route (*this, X_("toBeResetFroXML"), flags)); - if (r->init () == 0 && r->set_state (node, version) == 0) { - BOOST_MARK_ROUTE (r); - ret = r; - } + if (r->init () == 0 && r->set_state (node, version) == 0) { + BOOST_MARK_ROUTE (r); + ret = r; + } } return ret; @@ -1794,11 +1846,7 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version) } DataType type = DataType::AUDIO; - XMLProperty const * prop = node.property("default-type"); - - if (prop) { - type = DataType (prop->value()); - } + node.get_property("default-type", type); assert (type != DataType::NIL); @@ -1816,33 +1864,33 @@ Session::XMLRouteFactory_2X (const XMLNode& node, int version) boost::shared_ptr 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); BOOST_MARK_TRACK (track); - ret = track; + ret = track; } else { PresentationInfo::Flag flags = PresentationInfo::get_flags (node); boost::shared_ptr r (new Route (*this, X_("toBeResetFroXML"), flags)); - if (r->init () == 0 && r->set_state (node, version) == 0) { - BOOST_MARK_ROUTE (r); - ret = r; - } + if (r->init () == 0 && r->set_state (node, version) == 0) { + BOOST_MARK_ROUTE (r); + ret = r; + } } return ret; @@ -1964,11 +2012,11 @@ Session::XMLRegionFactory (const XMLNode& node, bool full) } } - if (!type || type->value() == "audio") { - return boost::shared_ptr(XMLAudioRegionFactory (node, full)); - } else if (type->value() == "midi") { - return boost::shared_ptr(XMLMidiRegionFactory (node, full)); - } + if (!type || type->value() == "audio") { + return boost::shared_ptr(XMLAudioRegionFactory (node, full)); + } else if (type->value() == "midi") { + return boost::shared_ptr(XMLMidiRegionFactory (node, full)); + } } catch (failed_constructor& err) { return boost::shared_ptr (); @@ -1992,9 +2040,7 @@ Session::XMLAudioRegionFactory (const XMLNode& node, bool /*full*/) return boost::shared_ptr(); } - 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"; @@ -2198,19 +2244,23 @@ Session::load_sources (const XMLNode& node) nlist = node.children(); set_dirty(); + std::map relocation; for (niter = nlist.begin(); niter != nlist.end(); ++niter) { #ifdef PLATFORM_WINDOWS int old_mode = 0; #endif + 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 @@ -2222,10 +2272,25 @@ retry: 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::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; + _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"), + error << string_compose (_("An external MIDI file is missing. %1 cannot currently recover from missing external MIDI files"), PROGRAM_NAME) << endmsg; return -1; } @@ -2238,7 +2303,19 @@ retry: switch (user_choice) { case 0: - /* user added a new search location, so try again */ + /* 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; @@ -2438,9 +2515,9 @@ Session::refresh_disk_space () vector scanned_volumes; vector::iterator j; vector::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; @@ -2778,7 +2855,7 @@ Session::route_group_by_name (string name) RouteGroup& Session::all_route_group() const { - return *_all_route_group; + return *_all_route_group; } void @@ -2908,27 +2985,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 @@ -3042,18 +3119,18 @@ Session::find_all_sources_across_snapshots (set& result, bool exclude_th } struct RegionCounter { - typedef std::map > AudioSourceList; - AudioSourceList::iterator iter; - boost::shared_ptr region; - uint32_t count; + typedef std::map > AudioSourceList; + AudioSourceList::iterator iter; + boost::shared_ptr region; + uint32_t count; - RegionCounter() : count (0) {} + RegionCounter() : count (0) {} }; int Session::ask_about_playlist_deletion (boost::shared_ptr p) { - boost::optional r = AskAboutPlaylistDeletion (p); + boost::optional r = AskAboutPlaylistDeletion (p); return r.get_value_or (1); } @@ -3111,7 +3188,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; } @@ -3206,10 +3283,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 */ @@ -3224,8 +3300,8 @@ 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)) { dead_sources.push_back (i->second); @@ -3256,9 +3332,9 @@ Session::cleanup_sources (CleanupReport& rep) find_files_matching_filter (candidates, midi_path, accept_all_midi_files, (void *) 0, true, true); /* add sources from all other snapshots as "used", but don't use this - snapshot because the state file on disk still references sources we - may have already dropped. - */ + snapshot because the state file on disk still references sources we + may have already dropped. + */ find_all_sources_across_snapshots (sources_used_by_all_snapshots, true); @@ -3272,12 +3348,12 @@ Session::cleanup_sources (CleanupReport& rep) playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot)); /* add our current source list - */ + */ for (SourceMap::iterator i = sources.begin(); i != sources.end(); ) { boost::shared_ptr fs; - SourceMap::iterator tmp = i; - ++tmp; + SourceMap::iterator tmp = i; + ++tmp; if ((fs = boost::dynamic_pointer_cast (i->second)) == 0) { /* not a file */ @@ -3307,11 +3383,9 @@ Session::cleanup_sources (CleanupReport& rep) cerr << "Source from source list found in used_by_this_snapshot (" << fs->path() << ")\n"; } else { cerr << "Source from source list NOT found in used_by_this_snapshot (" << fs->path() << ")\n"; - /* this source is NOT in use by this snapshot - */ + /* this source is NOT in use by this snapshot */ - /* remove all related regions from RegionFactory master list - */ + /* remove all related regions from RegionFactory master list */ RegionFactory::remove_regions_using_source (i->second); @@ -3327,12 +3401,12 @@ Session::cleanup_sources (CleanupReport& rep) } } - i = tmp; + i = tmp; } /* now check each candidate source to see if it exists in the list of - sources_used_by_all_snapshots. If it doesn't, put it into "unused". - */ + * sources_used_by_all_snapshots. If it doesn't, put it into "unused". + */ cerr << "Candidates: " << candidates.size() << endl; cerr << "Used by others: " << sources_used_by_all_snapshots.size() << endl; @@ -3376,9 +3450,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) { @@ -3424,8 +3498,8 @@ 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; } @@ -3434,24 +3508,23 @@ Session::cleanup_sources (CleanupReport& rep) 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; + 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 - */ + * or for the first channel of embedded files. it will miss + * some peakfiles for other channels + */ string peakpath = construct_peak_filepath (base); if (Glib::file_test (peakpath.c_str (), Glib::FILE_TEST_EXISTS)) { if (::g_unlink (peakpath.c_str ()) != 0) { error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"), peakpath, _path, - g_strerror (errno)) << endmsg; + g_strerror (errno)) << endmsg; /* try to back out */ ::g_rename (newpath.c_str (), _path.c_str ()); goto out; @@ -3467,13 +3540,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; @@ -3494,7 +3567,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; @@ -3503,19 +3576,18 @@ 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 @@ -3587,6 +3659,12 @@ Session::controllable_by_id (const PBD::ID& id) return boost::shared_ptr(); } +boost::shared_ptr +Session::automation_control_by_id (const PBD::ID& id) +{ + return boost::dynamic_pointer_cast (controllable_by_id (id)); +} + boost::shared_ptr Session::controllable_by_descriptor (const ControllableDescriptor& desc) { @@ -3649,7 +3727,7 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc) break; case ControllableDescriptor::Solo: - c = s->solo_control(); + c = s->solo_control(); break; case ControllableDescriptor::Mute: @@ -3831,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(); @@ -4019,6 +4105,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) { @@ -4114,30 +4204,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 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 */ @@ -4184,16 +4274,15 @@ Session::setup_midi_machine_control () boost::shared_ptr 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 @@ -4206,7 +4295,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); } @@ -4232,7 +4321,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 } @@ -4362,12 +4451,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; } @@ -5023,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);