X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession_state.cc;h=cb4a9e5813f25b61c7592b24ca4e513b82031797;hb=bc89fe0147c04b67141936d109c00dfd4d69cc4b;hp=24a335a12826ccb429f4c5bba10d18f793f0164c;hpb=ee29fcacfb750e8880ccafc88fff360e2331950e;p=ardour.git diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 24a335a128..cb4a9e5813 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -47,24 +47,24 @@ #endif #include +#include #include #include -#include -#include +#include #include #include #include -#include #include -#include #include #include #include #include #include +#include +#include #include #include #include @@ -78,15 +78,15 @@ #include #include #include -#include -#include +#include #include -#include +#include #include #include #include #include #include +#include #include #include #include @@ -101,6 +101,9 @@ #include #include #include +#include +#include + #include #include "i18n.h" @@ -137,10 +140,17 @@ Session::first_stage_init (string fullpath, string snapshot_name) _name = _current_snapshot_name = snapshot_name; + set_history_depth (Config->get_history_depth()); + _current_frame_rate = _engine.frame_rate (); + _nominal_frame_rate = _current_frame_rate; + _base_frame_rate = _current_frame_rate; + _tempo_map = new TempoMap (_current_frame_rate); _tempo_map->StateChanged.connect (mem_fun (*this, &Session::tempo_map_changed)); + + g_atomic_int_set (&processing_prohibited, 0); insert_cnt = 0; _transport_speed = 0; @@ -155,13 +165,14 @@ Session::first_stage_init (string fullpath, string snapshot_name) g_atomic_int_set (&_record_status, Disabled); loop_changing = false; play_loop = false; + have_looped = false; _last_roll_location = 0; _last_record_location = 0; pending_locate_frame = 0; pending_locate_roll = false; pending_locate_flush = false; - dstream_buffer_size = 0; - state_tree = 0; + audio_dstream_buffer_size = 0; + midi_dstream_buffer_size = 0; state_was_pending = false; set_next_event (); outbound_mtc_smpte_frame = 0; @@ -173,23 +184,20 @@ Session::first_stage_init (string fullpath, string snapshot_name) _worst_output_latency = 0; _worst_input_latency = 0; _worst_track_latency = 0; - _state_of_the_state = StateOfTheState(CannotSave|InitialConnecting|Loading|Deletion); + _state_of_the_state = StateOfTheState(CannotSave|InitialConnecting|Loading); + _slave = 0; - butler_mixdown_buffer = 0; - butler_gain_buffer = 0; - mmc = 0; session_send_mmc = false; session_send_mtc = false; post_transport_work = PostTransportWork (0); g_atomic_int_set (&butler_should_do_transport_work, 0); - g_atomic_int_set (&butler_active, 0); g_atomic_int_set (&_playback_load, 100); g_atomic_int_set (&_capture_load, 100); g_atomic_int_set (&_playback_load_min, 100); g_atomic_int_set (&_capture_load_min, 100); _play_range = false; - waiting_to_start = false; _exporting = false; + _exporting_realtime = false; _gain_automation_buffer = 0; _pan_automation_buffer = 0; _npan_buffers = 0; @@ -216,8 +224,6 @@ Session::first_stage_init (string fullpath, string snapshot_name) waveforms for clicks. */ - click_data = 0; - click_emphasis_data = 0; click_length = 0; click_emphasis_length = 0; _clicking = false; @@ -230,9 +236,6 @@ Session::first_stage_init (string fullpath, string snapshot_name) waiting_for_sync_offset = false; } - _current_frame_rate = 48000; - _base_frame_rate = 48000; - last_smpte_when = 0; _smpte_offset = 0; _smpte_offset_negative = true; @@ -247,7 +250,7 @@ Session::first_stage_init (string fullpath, string snapshot_name) /* slave stuff */ - average_slave_delta = 1800; + average_slave_delta = 1800; // !!! why 1800 ???? have_first_delta_accumulator = false; delta_accumulator_cnt = 0; slave_state = Stopped; @@ -259,13 +262,13 @@ Session::first_stage_init (string fullpath, string snapshot_name) RegionFactory::CheckNewRegion.connect (mem_fun (*this, &Session::add_region)); SourceFactory::SourceCreated.connect (mem_fun (*this, &Session::add_source)); PlaylistFactory::PlaylistCreated.connect (mem_fun (*this, &Session::add_playlist)); - Redirect::RedirectCreated.connect (mem_fun (*this, &Session::add_redirect)); + Processor::ProcessorCreated.connect (mem_fun (*this, &Session::add_processor)); NamedSelection::NamedSelectionCreated.connect (mem_fun (*this, &Session::add_named_selection)); AutomationList::AutomationListCreated.connect (mem_fun (*this, &Session::add_automation_list)); Controllable::Destroyed.connect (mem_fun (*this, &Session::remove_controllable)); - IO::MoreChannels.connect (mem_fun (*this, &Session::ensure_buffers)); + IO::PortCountChanged.connect (mem_fun (*this, &Session::ensure_buffers)); /* stop IO objects from doing stuff until we're ready for them */ @@ -277,7 +280,7 @@ Session::first_stage_init (string fullpath, string snapshot_name) int Session::second_stage_init (bool new_session) { - AudioFileSource::set_peak_dir (peak_dir()); + AudioFileSource::set_peak_dir (_session_dir->peak_path().to_string()); if (!new_session) { if (load_state (_current_snapshot_name)) { @@ -290,12 +293,13 @@ Session::second_stage_init (bool new_session) return -1; } - /*if (start_midi_thread ()) { + if (start_midi_thread ()) { return -1; - }*/ + } // set_state() will call setup_raid_path(), but if it's a new session we need // to call setup_raid_path() here. + if (state_tree) { if (set_state (*state_tree->root())) { return -1; @@ -316,7 +320,7 @@ Session::second_stage_init (bool new_session) _state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave|Loading); - // set_auto_input (true); + _locations.changed.connect (mem_fun (this, &Session::locations_changed)); _locations.added.connect (mem_fun (this, &Session::locations_added)); setup_click_sounds (0); @@ -343,10 +347,19 @@ Session::second_stage_init (bool new_session) return -1; } - //send_full_time_code (); + BootMessage (_("Reset Remote Controls")); + + send_full_time_code (0); _engine.transport_locate (0); deliver_mmc (MIDI::MachineControl::cmdMmcReset, 0); deliver_mmc (MIDI::MachineControl::cmdLocate, 0); + + MidiClockTicker::instance().set_session(*this); + MIDI::Name::MidiPatchManager::instance().set_session(*this); + + /* initial program change will be delivered later; see ::config_changed() */ + + BootMessage (_("Reset Control Protocols")); ControlProtocolManager::instance().set_session (*this); @@ -355,7 +368,19 @@ Session::second_stage_init (bool new_session) } else { _end_location_is_free = false; } + + _state_of_the_state = Clean; + + DirtyChanged (); /* EMIT SIGNAL */ + + if (state_was_pending) { + save_state (_current_snapshot_name); + remove_pending_capture_state (); + state_was_pending = false; + } + BootMessage (_("Session loading complete")); + return 0; } @@ -368,7 +393,7 @@ Session::raid_path () const raid_search_path += sys::path((*i).path); } - return raid_search_path.get_string (); + return raid_search_path.to_string (); } void @@ -405,49 +430,135 @@ Session::setup_raid_path (string path) // set the AudioFileSource and SMFSource search path - AudioFileSource::set_search_path (sound_search_path.get_string ()); - SMFSource::set_search_path (midi_search_path.get_string ()); + AudioFileSource::set_search_path (sound_search_path.to_string ()); + SMFSource::set_search_path (midi_search_path.to_string ()); + // reset the round-robin soundfile path thingie - + last_rr_session_dir = session_dirs.begin(); } -void -Session::initialize_start_and_end_locations (nframes_t start, nframes_t end) +int +Session::ensure_subdirs () { - start_location->set_end (start); - _locations.add (start_location); + string dir; - end_location->set_end (end); - _locations.add (end_location); -} + dir = session_directory().peak_path().to_string(); -bool -Session::create_session_file () -{ - _state_of_the_state = Clean; + if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session peakfile folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + return -1; + } + + dir = session_directory().sound_path().to_string(); + + if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session sounds dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + return -1; + } + + dir = session_directory().midi_path().to_string(); + + if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session midi dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + return -1; + } + + dir = session_directory().dead_sound_path().to_string(); - if (save_state (_current_snapshot_name)) { - error << "Could not create new session file" << endmsg; - return false; + if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session dead sounds folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + return -1; } - return true; + + dir = session_directory().export_path().to_string(); + + if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session export folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + return -1; + } + + dir = analysis_dir (); + + if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session analysis folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg; + return -1; + } + + return 0; } -bool -Session::create_session_file_from_template (const string& template_path) +int +Session::create (bool& new_session, const string& mix_template, nframes_t initial_length) { - string out_path = _path + _name + statefile_suffix; - if(!copy_file (template_path, out_path)) { - error << string_compose (_("Could not use session template %1 to create new session."), template_path) - << endmsg; - return false; + if (g_mkdir_with_parents (_path.c_str(), 0755) < 0) { + error << string_compose(_("Session: cannot create session folder \"%1\" (%2)"), _path, strerror (errno)) << endmsg; + return -1; + } + + if (ensure_subdirs ()) { + return -1; + } + + /* check new_session so we don't overwrite an existing one */ + + if (!mix_template.empty()) { + std::string in_path = mix_template; + + ifstream in(in_path.c_str()); + + if (in){ + string out_path = _path; + out_path += _name; + out_path += statefile_suffix; + + ofstream out(out_path.c_str()); + + if (out){ + out << in.rdbuf(); + + // okay, session is set up. Treat like normal saved + // session from now on. + + new_session = false; + return 0; + + } else { + error << string_compose (_("Could not open %1 for writing mix template"), out_path) + << endmsg; + return -1; + } + + } else { + error << string_compose (_("Could not open mix template %1 for reading"), in_path) + << endmsg; + return -1; + } + } - return true; + + /* Instantiate metadata */ + + _metadata = new SessionMetadata (); + + /* set initial start + end point */ + + start_location->set_end (0); + _locations.add (start_location); + + end_location->set_end (initial_length); + _locations.add (end_location); + + _state_of_the_state = Clean; + + save_state (""); + + return 0; } + int Session::load_diskstreams (const XMLNode& node) { @@ -491,13 +602,19 @@ Session::maybe_write_autosave() void Session::remove_pending_capture_state () { - string xml_path; - - xml_path = _path; - xml_path += _current_snapshot_name; - xml_path += pending_suffix; + sys::path pending_state_file_path(_session_dir->root_path()); + + pending_state_file_path /= _current_snapshot_name + pending_suffix; - unlink (xml_path.c_str()); + try + { + sys::remove (pending_state_file_path); + } + catch(sys::filesystem_error& ex) + { + error << string_compose(_("Could remove pending capture state at path \"%1\" (%2)"), + pending_state_file_path.to_string(), ex.what()) << endmsg; + } } /** Rename a state file. @@ -510,12 +627,21 @@ Session::rename_state (string old_name, string new_name) /* refuse to rename the current snapshot or the "main" one */ return; } - - const string old_xml_path = _path + old_name + statefile_suffix; - const string new_xml_path = _path + new_name + statefile_suffix; - if (rename (old_xml_path.c_str(), new_xml_path.c_str()) != 0) { - error << string_compose(_("could not rename snapshot %1 to %2"), old_name, new_name) << endmsg; + const string old_xml_filename = old_name + statefile_suffix; + const string new_xml_filename = new_name + statefile_suffix; + + const sys::path old_xml_path = _session_dir->root_path() / old_xml_filename; + const sys::path new_xml_path = _session_dir->root_path() / new_xml_filename; + + try + { + sys::rename (old_xml_path, new_xml_path); + } + catch (const sys::filesystem_error& err) + { + error << string_compose(_("could not rename snapshot %1 to %2 (%3)"), + old_name, new_name, err.what()) << endmsg; } } @@ -526,28 +652,29 @@ void Session::remove_state (string snapshot_name) { if (snapshot_name == _current_snapshot_name || snapshot_name == _name) { - /* refuse to remove the current snapshot or the "main" one */ + // refuse to remove the current snapshot or the "main" one return; } - - const string xml_path = _path + snapshot_name + statefile_suffix; - /* make a backup copy of the state file */ - const string bak_path = xml_path + ".bak"; - if (g_file_test (xml_path.c_str(), G_FILE_TEST_EXISTS)) { - copy_file (xml_path, bak_path); + sys::path xml_path(_session_dir->root_path()); + + xml_path /= snapshot_name + statefile_suffix; + + if (!create_backup_file (xml_path)) { + // don't remove it if a backup can't be made + // create_backup_file will log the error. + return; } - /* and delete it */ - unlink (xml_path.c_str()); + // and delete it + sys::remove (xml_path); } int Session::save_state (string snapshot_name, bool pending) { XMLTree tree; - string xml_path; - string bak_path; + sys::path xml_path(_session_dir->root_path()); if (_state_of_the_state & CannotSave) { return 1; @@ -559,6 +686,11 @@ Session::save_state (string snapshot_name, bool pending) return 1; } + /* tell sources we're saving first, in case they write out to a new file + * which should be saved with the state rather than the old one */ + for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) + i->second->session_saved(); + tree.set_root (&get_state()); if (snapshot_name.empty()) { @@ -568,52 +700,46 @@ Session::save_state (string snapshot_name, bool pending) if (!pending) { /* proper save: use statefile_suffix (.ardour in English) */ - xml_path = _path; - xml_path += snapshot_name; - xml_path += statefile_suffix; + + xml_path /= snapshot_name + statefile_suffix; /* make a backup copy of the old file */ - bak_path = xml_path; - bak_path += ".bak"; - - if (g_file_test (xml_path.c_str(), G_FILE_TEST_EXISTS)) { - copy_file (xml_path, bak_path); + + if (sys::exists(xml_path) && !create_backup_file (xml_path)) { + // create_backup_file will log the error + return -1; } } else { /* pending save: use pending_suffix (.pending in English) */ - xml_path = _path; - xml_path += snapshot_name; - xml_path += pending_suffix; - + xml_path /= snapshot_name + pending_suffix; } - string tmp_path; + sys::path tmp_path(_session_dir->root_path()); - tmp_path = _path; - tmp_path += snapshot_name; - tmp_path += ".tmp"; + tmp_path /= snapshot_name + temp_suffix; - // cerr << "actually writing state to " << xml_path << endl; + // cerr << "actually writing state to " << xml_path.to_string() << endl; - if (!tree.write (tmp_path)) { - error << string_compose (_("state could not be saved to %1"), tmp_path) << endmsg; - unlink (tmp_path.c_str()); + if (!tree.write (tmp_path.to_string())) { + error << string_compose (_("state could not be saved to %1"), tmp_path.to_string()) << endmsg; + sys::remove (tmp_path); return -1; } else { - if (rename (tmp_path.c_str(), xml_path.c_str()) != 0) { - error << string_compose (_("could not rename temporary session file %1 to %2"), tmp_path, xml_path) << endmsg; - unlink (tmp_path.c_str()); + if (rename (tmp_path.to_string().c_str(), xml_path.to_string().c_str()) != 0) { + error << string_compose (_("could not rename temporary session file %1 to %2"), + tmp_path.to_string(), xml_path.to_string()) << endmsg; + sys::remove (tmp_path); return -1; } } if (!pending) { - save_history (snapshot_name); + save_history (snapshot_name); bool was_dirty = dirty(); @@ -622,7 +748,7 @@ Session::save_state (string snapshot_name, bool pending) if (was_dirty) { DirtyChanged (); /* EMIT SIGNAL */ } - + StateSaved (snapshot_name); /* EMIT SIGNAL */ } @@ -647,17 +773,14 @@ Session::load_state (string snapshot_name) state_tree = 0; } - string xmlpath; - state_was_pending = false; /* check for leftover pending state from a crashed capture attempt */ - xmlpath = _path; - xmlpath += snapshot_name; - xmlpath += pending_suffix; + sys::path xmlpath(_session_dir->root_path()); + xmlpath /= snapshot_name + pending_suffix; - if (Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) { + if (sys::exists (xmlpath)) { /* there is pending state from a crashed capture attempt */ @@ -667,14 +790,12 @@ Session::load_state (string snapshot_name) } if (!state_was_pending) { - - xmlpath = _path; - xmlpath += snapshot_name; - xmlpath += statefile_suffix; + xmlpath = _session_dir->root_path(); + xmlpath /= snapshot_name + statefile_suffix; } - if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) { - error << string_compose(_("%1: session state information file \"%2\" doesn't exist!"), _name, xmlpath) << endmsg; + if (!sys::exists (xmlpath)) { + error << string_compose(_("%1: session state information file \"%2\" doesn't exist!"), _name, xmlpath.to_string()) << endmsg; return 1; } @@ -682,8 +803,8 @@ Session::load_state (string snapshot_name) set_dirty(); - if (!state_tree->read (xmlpath)) { - error << string_compose(_("Could not understand ardour file %1"), xmlpath) << endmsg; + if (!state_tree->read (xmlpath.to_string())) { + error << string_compose(_("Could not understand ardour file %1"), xmlpath.to_string()) << endmsg; delete state_tree; state_tree = 0; return -1; @@ -692,41 +813,52 @@ Session::load_state (string snapshot_name) XMLNode& root (*state_tree->root()); if (root.name() != X_("Session")) { - error << string_compose (_("Session file %1 is not an Ardour session"), xmlpath) << endmsg; + error << string_compose (_("Session file %1 is not an Ardour session"), xmlpath.to_string()) << endmsg; delete state_tree; state_tree = 0; return -1; } const XMLProperty* prop; - bool is_old = false; + bool is_old = false; // session is _very_ old (pre-2.0) if ((prop = root.property ("version")) == 0) { /* no version implies very old version of Ardour */ is_old = true; } else { int major_version; - major_version = atoi (prop->value()); // grab just the first number before the period + major_version = atoi (prop->value().c_str()); // grab just the first number before the period if (major_version < 2) { is_old = true; } } if (is_old) { - string backup_path; - backup_path = _path; - backup_path += snapshot_name; - backup_path += "-1"; - backup_path += statefile_suffix; + sys::path backup_path(_session_dir->root_path()); + + backup_path /= snapshot_name + "-1" + statefile_suffix; + + // only create a backup once + if (sys::exists (backup_path)) { + return 0; + } info << string_compose (_("Copying old session file %1 to %2\nUse %2 with Ardour versions before 2.0 from now on"), - xmlpath, backup_path) + xmlpath.to_string(), backup_path.to_string()) << endmsg; - copy_file (xmlpath, backup_path); - - /* if it fails, don't worry. right? */ + try + { + sys::copy_file (xmlpath, backup_path); + } + catch(sys::filesystem_error& ex) + { + error << string_compose (_("Unable to make backup of state file %1 (%2)"), + xmlpath.to_string(), ex.what()) + << endmsg; + return -1; + } } return 0; @@ -741,6 +873,12 @@ Session::load_options (const XMLNode& node) Config->set_variables (node, ConfigVariableBase::Session); + /* now reset MIDI ports because the session can have its own + MIDI configuration. + */ + + setup_midi (); + if ((child = find_named_node (node, "end-marker-is-free")) != 0) { if ((prop = child->property ("val")) != 0) { _end_location_is_free = (prop->value() == "yes"); @@ -800,16 +938,16 @@ Session::state(bool full_state) // store libardour version, just in case char buf[16]; - snprintf(buf, sizeof(buf)-1, "%d.%d.%d", - libardour_major_version, libardour_minor_version, libardour_micro_version); + snprintf(buf, sizeof(buf), "%d.%d.%d", libardour3_major_version, libardour3_minor_version, libardour3_micro_version); node->add_property("version", string(buf)); /* store configuration settings */ if (full_state) { - /* store the name */ node->add_property ("name", _name); + snprintf (buf, sizeof (buf), "%" PRId32, _nominal_frame_rate); + node->add_property ("sample-rate", buf); if (session_dirs.size() > 1) { @@ -850,6 +988,8 @@ Session::state(bool full_state) node->add_child_nocopy (get_options()); + node->add_child_nocopy (_metadata->get_state()); + child = node->add_child ("Sources"); if (full_state) { @@ -919,12 +1059,13 @@ Session::state(bool full_state) node->add_child_nocopy (loc.get_state()); } - child = node->add_child ("Connections"); + child = node->add_child ("Bundles"); { Glib::Mutex::Lock lm (bundle_lock); for (BundleList::iterator i = _bundles.begin(); i != _bundles.end(); ++i) { - if (!(*i)->dynamic()) { - child->add_child_nocopy ((*i)->get_state()); + boost::shared_ptr b = boost::dynamic_pointer_cast (*i); + if (b) { + child->add_child_nocopy (b->get_state()); } } } @@ -938,7 +1079,7 @@ Session::state(bool full_state) public_order.sort (cmp); for (RouteList::iterator i = public_order.begin(); i != public_order.end(); ++i) { - if (!(*i)->hidden()) { + if (!(*i)->is_hidden()) { if (full_state) { child->add_child_nocopy ((*i)->get_state()); } else { @@ -1037,7 +1178,18 @@ Session::set_state (const XMLNode& node) _name = prop->value (); } - setup_raid_path(_path); + if ((prop = node.property (X_("sample-rate"))) != 0) { + + _nominal_frame_rate = atoi (prop->value()); + + if (_nominal_frame_rate != _current_frame_rate) { + if (AskAboutSampleRateMismatch (_nominal_frame_rate, _current_frame_rate)) { + return -1; + } + } + } + + setup_raid_path(_session_dir->root_path().to_string()); if ((prop = node.property (X_("id-counter"))) != 0) { uint64_t x; @@ -1059,10 +1211,11 @@ Session::set_state (const XMLNode& node) /* Object loading order: - MIDI Control Path extra Options/Config + MIDI Control // relies on data from Options/Config + Metadata Locations Sources AudioRegions @@ -1075,9 +1228,6 @@ Session::set_state (const XMLNode& node) ControlProtocols */ - if (use_config_midi_ports ()) { - } - if ((child = find_named_node (node, "extra")) != 0) { _extra_xml = new XMLNode (*child); } @@ -1090,6 +1240,15 @@ Session::set_state (const XMLNode& node) error << _("Session: XML state has no options section") << endmsg; } + if (use_config_midi_ports ()) { + } + + if ((child = find_named_node (node, "Metadata")) == 0) { + warning << _("Session: XML state has no metadata section (2.0 session?)") << endmsg; + } else if (_metadata->set_state (*child)) { + goto out; + } + if ((child = find_named_node (node, "Locations")) == 0) { error << _("Session: XML state has no locations section") << endmsg; goto out; @@ -1163,13 +1322,16 @@ Session::set_state (const XMLNode& node) goto out; } - if ((child = find_named_node (node, "Connections")) == 0) { - error << _("Session: XML state has no connections section") << endmsg; - goto out; - } else if (load_bundles (*child)) { - goto out; + if ((child = find_named_node (node, "Bundles")) == 0) { + warning << _("Session: XML state has no bundles section (2.0 session?)") << endmsg; + //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 */ + _bundle_xml_node = new XMLNode (*child); } - + if ((child = find_named_node (node, "EditGroups")) == 0) { error << _("Session: XML state has no edit groups section") << endmsg; goto out; @@ -1203,7 +1365,7 @@ Session::set_state (const XMLNode& node) } else if (_click_io) { _click_io->set_state (*child); } - + if ((child = find_named_node (node, "ControlProtocols")) != 0) { ControlProtocolManager::instance().set_protocol_states (*child); } @@ -1212,14 +1374,6 @@ Session::set_state (const XMLNode& node) StateReady (); /* EMIT SIGNAL */ - _state_of_the_state = Clean; - - if (state_was_pending) { - save_state (_current_snapshot_name); - remove_pending_capture_state (); - state_was_pending = false; - } - return 0; out: @@ -1246,10 +1400,12 @@ Session::load_routes (const XMLNode& node) return -1; } + BootMessage (string_compose (_("Loaded track/bus %1"), route->name())); + new_routes.push_back (route); } - add_routes (new_routes); + add_routes (new_routes, false); return 0; } @@ -1297,7 +1453,14 @@ Session::load_regions (const XMLNode& node) for (niter = nlist.begin(); niter != nlist.end(); ++niter) { if ((region = XMLRegionFactory (**niter, false)) == 0) { - error << _("Session: cannot create Region from XML description.") << endmsg; + error << _("Session: cannot create Region from XML description."); + const XMLProperty *name = (**niter).property("name"); + + if (name) { + error << " " << string_compose (_("Can not load state for region '%1'"), name->value()); + } + + error << endmsg; } } @@ -1335,6 +1498,7 @@ Session::XMLAudioRegionFactory (const XMLNode& node, bool full) boost::shared_ptr source; boost::shared_ptr as; SourceList sources; + SourceList master_sources; uint32_t nchans = 1; char buf[128]; @@ -1394,7 +1558,27 @@ Session::XMLAudioRegionFactory (const XMLNode& node, bool full) sources.push_back (as); } } - + + for (uint32_t n=1; n < nchans; ++n) { + snprintf (buf, sizeof(buf), X_("master-source-%d"), n); + if ((prop = node.property (buf)) != 0) { + + PBD::ID id2 (prop->value()); + + if ((source = source_by_id (id2)) == 0) { + error << string_compose(_("Session: XMLNode describing a AudioRegion references an unknown source id =%1"), id2) << endmsg; + return boost::shared_ptr(); + } + + as = boost::dynamic_pointer_cast(source); + if (!as) { + error << string_compose(_("Session: XMLNode describing a AudioRegion references a non-audio source id =%1"), id2) << endmsg; + return boost::shared_ptr(); + } + master_sources.push_back (as); + } + } + try { boost::shared_ptr region (boost::dynamic_pointer_cast (RegionFactory::create (sources, node))); @@ -1409,6 +1593,14 @@ Session::XMLAudioRegionFactory (const XMLNode& node, bool full) } } + if (!master_sources.empty()) { + if (master_sources.size() == nchans) { + error << _("Session: XMLNode describing an AudioRegion is missing some master sources; ignored") << endmsg; + } else { + region->set_master_sources (master_sources); + } + } + return region; } @@ -1501,23 +1693,29 @@ Session::get_sources_as_xml () } string -Session::path_from_region_name (string name, string identifier) +Session::path_from_region_name (DataType type, string name, string identifier) { char buf[PATH_MAX+1]; uint32_t n; SessionDirectory sdir(get_best_session_directory_for_new_source()); - string sound_dir = sdir.sound_path().to_string(); + sys::path source_dir = ((type == DataType::AUDIO) + ? sdir.sound_path() : sdir.midi_path()); + + string ext = ((type == DataType::AUDIO) ? ".wav" : ".mid"); for (n = 0; n < 999999; ++n) { if (identifier.length()) { - snprintf (buf, sizeof(buf), "%s/%s%s%" PRIu32 ".wav", sound_dir.c_str(), name.c_str(), - identifier.c_str(), n); + snprintf (buf, sizeof(buf), "%s%s%" PRIu32 "%s", name.c_str(), + identifier.c_str(), n, ext.c_str()); } else { - snprintf (buf, sizeof(buf), "%s/%s-%" PRIu32 ".wav", sound_dir.c_str(), name.c_str(), n); + snprintf (buf, sizeof(buf), "%s-%" PRIu32 "%s", name.c_str(), + n, ext.c_str()); } - if (!Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) { - return buf; + sys::path source_path = source_dir / buf; + + if (!sys::exists (source_path)) { + return source_path.to_string(); } } @@ -1565,7 +1763,8 @@ Session::XMLSourceFactory (const XMLNode& node) } try { - return SourceFactory::create (*this, node); + /* note: do peak building in another thread when loading session state */ + return SourceFactory::create (*this, node, true); } catch (failed_constructor& err) { @@ -1578,40 +1777,37 @@ int Session::save_template (string template_name) { XMLTree tree; - string xml_path, bak_path, template_path; if (_state_of_the_state & CannotSave) { return -1; } - DIR* dp; - string dir = template_dir(); + sys::path user_template_dir(user_template_directory()); - if ((dp = opendir (dir.c_str()))) { - closedir (dp); - } else { - if (g_mkdir_with_parents (dir.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { - error << string_compose(_("Could not create mix templates directory \"%1\" (%2)"), dir, strerror (errno)) << endmsg; - return -1; - } + try + { + sys::create_directories (user_template_dir); + } + catch(sys::filesystem_error& ex) + { + error << string_compose(_("Could not create mix templates directory \"%1\" (%2)"), + user_template_dir.to_string(), ex.what()) << endmsg; + return -1; } tree.set_root (&get_template()); - xml_path = dir; - xml_path += template_name; - xml_path += template_suffix; + sys::path template_file_path(user_template_dir); + template_file_path /= template_name + template_suffix; - ifstream in(xml_path.c_str()); - - if (in) { - warning << string_compose(_("Template \"%1\" already exists - new version not created"), template_name) << endmsg; + if (sys::exists (template_file_path)) + { + warning << string_compose(_("Template \"%1\" already exists - new version not created"), + template_file_path.to_string()) << endmsg; return -1; - } else { - in.close(); } - if (!tree.write (xml_path)) { + if (!tree.write (template_file_path.to_string())) { error << _("mix template not saved") << endmsg; return -1; } @@ -1622,20 +1818,38 @@ Session::save_template (string template_name) int Session::rename_template (string old_name, string new_name) { - string old_path = template_dir() + old_name + template_suffix; - string new_path = template_dir() + new_name + template_suffix; + sys::path old_path (user_template_directory()); + old_path /= old_name + template_suffix; + + sys::path new_path(user_template_directory()); + new_path /= new_name + template_suffix; + + if (sys::exists (new_path)) { + warning << string_compose(_("Template \"%1\" already exists - template not renamed"), + new_path.to_string()) << endmsg; + return -1; + } - return rename (old_path.c_str(), new_path.c_str()); + try { + sys::rename (old_path, new_path); + return 0; + } catch (...) { + return -1; + } } int Session::delete_template (string name) { - string template_path = template_dir(); - template_path += name; - template_path += template_suffix; + sys::path path = user_template_directory(); + path /= name + template_suffix; - return remove (template_path.c_str()); + try { + sys::remove (path); + return 0; + } catch (...) { + return -1; + } } void @@ -1850,199 +2064,36 @@ Session::XMLNamedSelectionFactory (const XMLNode& node) } } -string -Session::old_sound_dir (bool with_path) const -{ - string res; - - if (with_path) { - res = _path; - } - - res += old_sound_dir_name; - - return res; -} - -string -Session::sound_dir (bool with_path) const -{ - string res; - string full; - - if (with_path) { - res = _path; - } else { - full = _path; - } - - res += interchange_dir_name; - res += '/'; - res += legalize_for_path (_name); - res += '/'; - res += sound_dir_name; - - if (with_path) { - full = res; - } else { - full += res; - } - - /* if this already exists, don't check for the old session sound directory */ - - if (Glib::file_test (full, Glib::FILE_TEST_IS_DIR|Glib::FILE_TEST_EXISTS)) { - return res; - } - - /* possibly support old session structure */ - - string old_nopath; - string old_withpath; - - old_nopath += old_sound_dir_name; - old_nopath += '/'; - - old_withpath = _path; - old_withpath += old_sound_dir_name; - - if (Glib::file_test (old_withpath.c_str(), Glib::FILE_TEST_IS_DIR|Glib::FILE_TEST_EXISTS)) { - if (with_path) - return old_withpath; - - return old_nopath; - } - - /* ok, old "sounds" directory isn't there, return the new path */ - - return res; -} - -string -Session::midi_dir (bool with_path) const -{ - string res; - string full; - - if (with_path) { - res = _path; - } else { - full = _path; - } - - res += interchange_dir_name; - res += '/'; - res += legalize_for_path (_name); - res += '/'; - res += midi_dir_name; - - if (with_path) { - full = res; - } else { - full += res; - } - - return res; -} - -string -Session::peak_dir () const -{ - string res = _path; - res += peak_dir_name; - res += '/'; - return res; -} - string Session::automation_dir () const { - string res = _path; - res += "automation/"; - return res; -} - -string -Session::template_dir () -{ - string path = get_user_ardour_path(); - path += "templates/"; - - return path; -} - -string -Session::export_dir () const -{ - string res = _path; - res += export_dir_name; - res += '/'; - return res; + return Glib::build_filename (_path, "automation"); } string -Session::suffixed_search_path (string suffix, bool data) +Session::analysis_dir () const { - string path; - - path += get_user_ardour_path(); - if (path[path.length()-1] != ':') { - path += ':'; - } - - if (data) { - path += get_system_data_path(); - } else { - path += get_system_module_path(); - } - - vector split_path; - - split (path, split_path, ':'); - path = ""; - - for (vector::iterator i = split_path.begin(); i != split_path.end(); ++i) { - path += *i; - path += suffix; - path += '/'; - - if (distance (i, split_path.end()) != 1) { - path += ':'; - } - } - - return path; -} - -string -Session::template_path () -{ - return suffixed_search_path (templates_dir_name, true); -} - -string -Session::control_protocol_path () -{ - return suffixed_search_path (surfaces_dir_name, false); + return Glib::build_filename (_path, "analysis"); } int -Session::load_bundles (const XMLNode& node) +Session::load_bundles (XMLNode const & node) { - XMLNodeList nlist = node.children(); - XMLNodeConstIterator niter; + XMLNodeList nlist = node.children(); + XMLNodeConstIterator niter; - set_dirty(); + set_dirty(); - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - if ((*niter)->name() == "InputConnection") { - add_bundle (new ARDOUR::InputBundle (**niter)); - } else if ((*niter)->name() == "OutputConnection") { - add_bundle (new ARDOUR::OutputBundle (**niter)); - } else { - error << string_compose(_("Unknown node \"%1\" found in Connections list from state file"), (*niter)->name()) << endmsg; - return -1; - } - } + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { + if ((*niter)->name() == "InputBundle") { + add_bundle (boost::shared_ptr (new UserBundle (**niter, true))); + } else if ((*niter)->name() == "OutputBundle") { + add_bundle (boost::shared_ptr (new UserBundle (**niter, false))); + } else { + error << string_compose(_("Unknown node \"%1\" found in Bundles list from state file"), (*niter)->name()) << endmsg; + return -1; + } + } return 0; } @@ -2083,6 +2134,12 @@ Session::load_route_groups (const XMLNode& node, bool edit) return 0; } +void +Session::auto_save() +{ + save_state (_current_snapshot_name); +} + static bool state_file_filter (const string &str, void *arg) { @@ -2133,12 +2190,6 @@ Session::possible_states () const return possible_states(_path); } -void -Session::auto_save() -{ - save_state (_current_snapshot_name); -} - RouteGroup * Session::add_edit_group (string name) { @@ -2214,7 +2265,7 @@ Session::edit_group_by_name (string name) } void -Session::begin_reversible_command (string name) +Session::begin_reversible_command (const string& name) { current_trans = new UndoTransaction; current_trans->set_name (name); @@ -2229,6 +2280,10 @@ Session::commit_reversible_command (Command *cmd) current_trans->add_command (cmd); } + if (current_trans->empty()) { + return; + } + gettimeofday (&now, 0); current_trans->set_timestamp (now); @@ -2242,7 +2297,7 @@ Session::get_global_route_boolean (bool (Route::*method)(void) const) boost::shared_ptr r = routes.reader (); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - if (!(*i)->hidden()) { + if (!(*i)->is_hidden()) { RouteBooleanState v; v.first =* i; @@ -2263,7 +2318,7 @@ Session::get_global_route_metering () boost::shared_ptr r = routes.reader (); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - if (!(*i)->hidden()) { + if (!(*i)->is_hidden()) { RouteMeterState v; v.first =* i; @@ -2347,38 +2402,6 @@ Session::global_record_enable_memento (void* src) } #endif -static bool -template_filter (const string &str, void *arg) -{ - return (str.length() > strlen(template_suffix) && - str.find (template_suffix) == (str.length() - strlen (template_suffix))); -} - -void -Session::get_template_list (list &template_names) -{ - vector *templates; - PathScanner scanner; - string path; - - path = template_path (); - - templates = scanner (path, template_filter, 0, false, true); - - vector::iterator i; - for (i = templates->begin(); i != templates->end(); ++i) { - string fullpath = *(*i); - int start, end; - - start = fullpath.find_last_of ('/') + 1; - if ((end = fullpath.find_last_of ('.')) <0) { - end = fullpath.length(); - } - - template_names.push_back(fullpath.substr(start, (end-start))); - } -} - static bool accept_all_non_peak_files (const string& path, void *arg) { @@ -2504,6 +2527,7 @@ Session::cleanup_sources (Session::cleanup_report& rep) int ret = -1; _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup); + /* step 1: consider deleting all unused playlists */ @@ -2667,7 +2691,7 @@ Session::cleanup_sources (Session::cleanup_report& rep) newpath += dead_sound_dir_name; if (g_mkdir_with_parents (newpath.c_str(), 0755) < 0) { - error << string_compose(_("Session: cannot create session peakfile dir \"%1\" (%2)"), newpath, strerror (errno)) << endmsg; + error << string_compose(_("Session: cannot create session peakfile folder \"%1\" (%2)"), newpath, strerror (errno)) << endmsg; return -1; } @@ -2743,6 +2767,7 @@ Session::cleanup_sources (Session::cleanup_report& rep) out: _state_of_the_state = (StateOfTheState) (_state_of_the_state & ~InCleanup); + return ret; } @@ -2816,6 +2841,7 @@ Session::set_dirty () _state_of_the_state = StateOfTheState (_state_of_the_state | Dirty); + if (!was_dirty) { DirtyChanged(); /* EMIT SIGNAL */ } @@ -2829,6 +2855,7 @@ Session::set_clean () _state_of_the_state = Clean; + if (was_dirty) { DirtyChanged(); /* EMIT SIGNAL */ } @@ -2838,10 +2865,11 @@ void Session::set_deletion_in_progress () { _state_of_the_state = StateOfTheState (_state_of_the_state | Deletion); + } void -Session::add_controllable (Controllable* c) +Session::add_controllable (boost::shared_ptr c) { /* this adds a controllable to the list managed by the Session. this is a subset of those managed by the Controllable class @@ -2852,6 +2880,8 @@ Session::add_controllable (Controllable* c) Glib::Mutex::Lock lm (controllables_lock); controllables.insert (c); } + +struct null_deleter { void operator()(void const *) const {} }; void Session::remove_controllable (Controllable* c) @@ -2862,14 +2892,15 @@ Session::remove_controllable (Controllable* c) Glib::Mutex::Lock lm (controllables_lock); - Controllables::iterator x = controllables.find (c); + Controllables::iterator x = controllables.find( + boost::shared_ptr(c, null_deleter())); if (x != controllables.end()) { controllables.erase (x); } } -Controllable* +boost::shared_ptr Session::controllable_by_id (const PBD::ID& id) { Glib::Mutex::Lock lm (controllables_lock); @@ -2880,90 +2911,106 @@ Session::controllable_by_id (const PBD::ID& id) } } - return 0; + return boost::shared_ptr(); } void -Session::add_instant_xml (XMLNode& node, const std::string& dir) +Session::add_instant_xml (XMLNode& node, bool write_to_config) { - Stateful::add_instant_xml (node, dir); - Config->add_instant_xml (node, get_user_ardour_path()); + Stateful::add_instant_xml (node, _path); + if (write_to_config) { + Config->add_instant_xml (node); + } } +XMLNode* +Session::instant_xml (const string& node_name) +{ + return Stateful::instant_xml (node_name, _path); +} int Session::save_history (string snapshot_name) { - XMLTree tree; - string xml_path; - string bak_path; - - tree.set_root (&_history.get_state (Config->get_saved_history_depth())); - - if (snapshot_name.empty()) { - snapshot_name = _current_snapshot_name; - } + XMLTree tree; + + if (snapshot_name.empty()) { + snapshot_name = _current_snapshot_name; + } + + const string history_filename = snapshot_name + history_suffix; + const string backup_filename = history_filename + backup_suffix; + const sys::path xml_path = _session_dir->root_path() / history_filename; + const sys::path backup_path = _session_dir->root_path() / backup_filename; - xml_path = _path + snapshot_name + ".history"; + if (sys::exists (xml_path)) { + try + { + sys::rename (xml_path, backup_path); + } + catch (const sys::filesystem_error& err) + { + error << _("could not backup old history file, current history not saved") << endmsg; + return -1; + } + } - bak_path = xml_path + ".bak"; - if ((access (xml_path.c_str(), F_OK) == 0) && - (rename (xml_path.c_str(), bak_path.c_str()))) - { - error << _("could not backup old history file, current history not saved.") << endmsg; - return -1; - } + if (!Config->get_save_history() || Config->get_saved_history_depth() < 0) { + return 0; + } - if (!tree.write (xml_path)) - { - error << string_compose (_("history could not be saved to %1"), xml_path) << endmsg; + tree.set_root (&_history.get_state (Config->get_saved_history_depth())); - /* don't leave a corrupt file lying around if it is - * possible to fix. - */ + if (!tree.write (xml_path.to_string())) + { + error << string_compose (_("history could not be saved to %1"), xml_path.to_string()) << endmsg; - if (unlink (xml_path.c_str())) { - error << string_compose (_("could not remove corrupt history file %1"), xml_path) << endmsg; - } else { - if (rename (bak_path.c_str(), xml_path.c_str())) + try { - error << string_compose (_("could not restore history file from backup %1"), bak_path) << endmsg; + sys::remove (xml_path); + sys::rename (backup_path, xml_path); + } + catch (const sys::filesystem_error& err) + { + error << string_compose (_("could not restore history file from backup %1 (%2)"), + backup_path.to_string(), err.what()) << endmsg; } - } - return -1; - } + return -1; + } - return 0; + return 0; } int Session::restore_history (string snapshot_name) { - XMLTree tree; - string xmlpath; + XMLTree tree; - if (snapshot_name.empty()) { - snapshot_name = _current_snapshot_name; - } + if (snapshot_name.empty()) { + snapshot_name = _current_snapshot_name; + } - /* read xml */ - xmlpath = _path + snapshot_name + ".history"; - cerr << string_compose(_("Loading history from '%1'."), xmlpath) << endmsg; + const string xml_filename = snapshot_name + history_suffix; + const sys::path xml_path = _session_dir->root_path() / xml_filename; - if (access (xmlpath.c_str(), F_OK)) { - info << string_compose (_("%1: no history file \"%2\" for this session."), _name, xmlpath) << endmsg; - return 1; - } + cerr << "Loading history from " << xml_path.to_string() << endmsg; - if (!tree.read (xmlpath)) { - error << string_compose (_("Could not understand session history file \"%1\""), xmlpath) << endmsg; - return -1; - } + if (!sys::exists (xml_path)) { + info << string_compose (_("%1: no history file \"%2\" for this session."), + _name, xml_path.to_string()) << endmsg; + return 1; + } - /* replace history */ - _history.clear(); + if (!tree.read (xml_path.to_string())) { + error << string_compose (_("Could not understand session history file \"%1\""), + xml_path.to_string()) << endmsg; + return -1; + } + + // replace history + _history.clear(); for (XMLNodeConstIterator it = tree.root()->children().begin(); it != tree.root()->children().end(); it++) { @@ -2972,22 +3019,21 @@ Session::restore_history (string snapshot_name) struct timeval tv; ut->set_name(t->property("name")->value()); - stringstream ss(t->property("tv_sec")->value()); + stringstream ss(t->property("tv-sec")->value()); ss >> tv.tv_sec; - ss.str(t->property("tv_usec")->value()); + ss.str(t->property("tv-usec")->value()); ss >> tv.tv_usec; ut->set_timestamp(tv); for (XMLNodeConstIterator child_it = t->children().begin(); - child_it != t->children().end(); - child_it++) + child_it != t->children().end(); child_it++) { XMLNode *n = *child_it; Command *c; if (n->name() == "MementoCommand" || - n->name() == "MementoUndoCommand" || - n->name() == "MementoRedoCommand") { + n->name() == "MementoUndoCommand" || + n->name() == "MementoRedoCommand") { if ((c = memento_command_factory(n))) { ut->add_command(c); @@ -2999,8 +3045,16 @@ Session::restore_history (string snapshot_name) ut->add_command (c); } + } else if (n->name() == "DeltaCommand") { + PBD::ID id(n->property("midi-source")->value()); + boost::shared_ptr midi_source = + boost::dynamic_pointer_cast(source_by_id(id)); + if(midi_source) { + ut->add_command(new MidiModel::DeltaCommand(midi_source->model(), *n)); + } else { + error << "FIXME: Failed to downcast MidiSource for DeltaCommand" << endmsg; + } } else { - error << string_compose(_("Couldn't figure out how to make a Command out of a %1 XMLNode."), n->name()) << endmsg; } } @@ -3078,10 +3132,16 @@ Session::config_changed (const char* parameter_name) //poke_midi_thread (); - } else if (PARAM_IS ("mmc-device-id")) { + } else if (PARAM_IS ("mmc-device-id") || PARAM_IS ("mmc-receive-id")) { + + if (mmc) { + mmc->set_receive_device_id (Config->get_mmc_receive_device_id()); + } + + } else if (PARAM_IS ("mmc-send-id")) { if (mmc) { - mmc->set_device_id (Config->get_mmc_device_id()); + mmc->set_send_device_id (Config->get_mmc_send_device_id()); } } else if (PARAM_IS ("midi-control")) { @@ -3195,6 +3255,31 @@ Session::config_changed (const char* parameter_name) set_remote_control_ids (); } else if (PARAM_IS ("denormal-model")) { setup_fpu (); + } else if (PARAM_IS ("history-depth")) { + set_history_depth (Config->get_history_depth()); + } else if (PARAM_IS ("sync-all-route-ordering")) { + sync_order_keys ("session"); + } else if (PARAM_IS ("initial-program-change")) { + + if (_mmc_port && Config->get_initial_program_change() >= 0) { + MIDI::byte buf[2]; + + buf[0] = MIDI::program; // channel zero by default + buf[1] = (Config->get_initial_program_change() & 0x7f); + + _mmc_port->midimsg (buf, sizeof (buf), 0); + } + } else if (PARAM_IS ("initial-program-change")) { + + if (_mmc_port && Config->get_initial_program_change() >= 0) { + MIDI::byte* buf = new MIDI::byte[2]; + + buf[0] = MIDI::program; // channel zero by default + buf[1] = (Config->get_initial_program_change() & 0x7f); + // deliver_midi (_mmc_port, buf, 2); + } + } else if (PARAM_IS ("solo-mute-override")) { + catch_up_on_solo_mute_override (); } set_dirty (); @@ -3202,3 +3287,9 @@ Session::config_changed (const char* parameter_name) #undef PARAM_IS } + +void +Session::set_history_depth (uint32_t d) +{ + _history.set_depth (d); +}