#include <stdint.h>
#include <algorithm>
-#include <fstream>
#include <string>
#include <cerrno>
#include <cstdio> /* snprintf(3) ... grrr */
#include <cmath>
+
#include <unistd.h>
-#include <sys/stat.h>
#include <climits>
#include <signal.h>
#include <sys/time.h>
#include <sys/vfs.h>
#endif
-#ifdef __APPLE__
+#if defined(__APPLE__) || defined(__FreeBSD__)
#include <sys/param.h>
#include <sys/mount.h>
#endif
#endif
#include <glib.h>
-#include <glib/gstdio.h>
+#include "pbd/gstdio_compat.h"
#include <glibmm.h>
#include <glibmm/threads.h>
#include "evoral/SMF.hpp"
-#include "pbd/boost_debug.h"
#include "pbd/basename.h"
-#include "pbd/controllable_descriptor.h"
#include "pbd/debug.h"
#include "pbd/enumwriter.h"
#include "pbd/error.h"
+#include "pbd/file_archive.h"
#include "pbd/file_utils.h"
#include "pbd/pathexpand.h"
#include "pbd/pthread_utils.h"
#include "pbd/stacktrace.h"
#include "pbd/convert.h"
#include "pbd/localtime_r.h"
+#include "pbd/unwind.h"
#include "ardour/amp.h"
#include "ardour/async_midi_port.h"
#include "ardour/audioengine.h"
#include "ardour/audiofilesource.h"
#include "ardour/audioregion.h"
+#include "ardour/auditioner.h"
#include "ardour/automation_control.h"
+#include "ardour/boost_debug.h"
#include "ardour/butler.h"
+#include "ardour/controllable_descriptor.h"
#include "ardour/control_protocol_manager.h"
#include "ardour/directory_names.h"
#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"
#include "ardour/playlist_source.h"
#include "ardour/port.h"
#include "ardour/processor.h"
+#include "ardour/progress.h"
#include "ardour/profile.h"
#include "ardour/proxy_controllable.h"
#include "ardour/recent_sessions.h"
#include "ardour/region_factory.h"
+#include "ardour/revision.h"
#include "ardour/route_group.h"
#include "ardour/send.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "ardour/ticker.h"
#include "ardour/user_bundle.h"
+#include "ardour/vca.h"
+#include "ardour/vca_manager.h"
#include "control_protocol/control_protocol.h"
-#include "i18n.h"
+#include "LuaBridge/LuaBridge.h"
+
+#include "pbd/i18n.h"
#include <locale.h>
using namespace std;
string full_session_name = Glib::build_filename( fullpath, _name );
full_session_name += statefile_suffix;
-
+
_is_new = !Glib::file_test (full_session_name, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
} else {
_is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
last_rr_session_dir = session_dirs.begin();
set_history_depth (Config->get_history_depth());
-
+
/* default: assume simple stereo speaker configuration */
_speakers->setup_default_speakers (2);
BootMessage (_("Using configuration"));
_midi_ports = new MidiPortManager;
-
+
MIDISceneChanger* msc;
_scene_changer = msc = new MIDISceneChanger (*this);
- msc->set_input_port (scene_input_port());
- msc->set_output_port (scene_out());
+ 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::dynamic_pointer_cast<AsyncMIDIPort>(scene_in())->set_timer (timer_func);
+ 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 ();
-
+
if (_butler->start_thread()) {
+ error << _("Butler did not start") << endmsg;
return -1;
}
-
+
if (start_midi_thread ()) {
+ error << _("MIDI I/O thread did not start") << endmsg;
return -1;
}
-
+
setup_click_sounds (0);
setup_midi_control ();
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::tempo_map_changed, this, _1));
+
/* MidiClock requires a tempo map */
+ delete midi_clock;
midi_clock = new MidiClockTicker ();
midi_clock->set_session (this);
SndFileSource::setup_standard_crossfades (*this, frame_rate());
_engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this));
-
+ _engine.MidiSelectionPortsChanged.connect_same_thread (*this, boost::bind (&Session::rewire_midi_selection_ports, this));
+
AudioDiskstream::allocate_working_buffers();
refresh_disk_space ();
-
+
/* we're finally ready to call set_state() ... all objects have
* been created, the engine is running.
*/
-
+
if (state_tree) {
if (set_state (*state_tree->root(), Stateful::loading_state_version)) {
+ error << _("Could not set session state from XML") << endmsg;
return -1;
}
} else {
boost::function<void (std::string)> ff (boost::bind (&Session::config_changed, this, _1, false));
boost::function<void (std::string)> ft (boost::bind (&Session::config_changed, this, _1, true));
-
+
Config->map_parameters (ff);
config.map_parameters (ft);
_butler->map_parameters ();
/* Reset all panners */
-
+
Delivery::reset_panners ();
-
+
/* this will cause the CPM to instantiate any protocols that are in use
* (or mandatory), which will pass it this Session, and then call
* set_state() on each instantiated protocol to match stored state.
*/
-
+
ControlProtocolManager::instance().set_session (this);
-
+
/* This must be done after the ControlProtocolManager set_session above,
as it will set states for ports which the ControlProtocolManager creates.
*/
-
+
// XXX set state of MIDI::Port's
// MidiPortManager::instance()->set_port_states (Config->midi_port_states ());
-
+
/* And this must be done after the MIDI::Manager::set_port_states as
* it will try to make connections whose details are loaded by set_port_states.
*/
-
+
hookup_io ();
-
+
/* Let control protocols know that we are now all connected, so they
* could start talking to surfaces if they want to.
*/
-
+
ControlProtocolManager::instance().midi_connectivity_established ();
-
+
if (_is_new && !no_auto_connect()) {
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock());
auto_connect_master_bus ();
}
-
+
_state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty));
-
+
/* update latencies */
-
+
initialize_latencies ();
-
+
_locations->added.connect_same_thread (*this, boost::bind (&Session::location_added, this, _1));
_locations->removed.connect_same_thread (*this, boost::bind (&Session::location_removed, this, _1));
_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;
+ } catch (std::exception const & e) {
+ error << _("Unexpected exception during session setup: ") << e.what() << endmsg;
+ return -1;
} catch (...) {
+ error << _("Unknown exception during session setup") << endmsg;
return -1;
}
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
send_immediate_mmc (MIDI::MachineControlCommand (Timecode::Time ()));
- MIDI::Name::MidiPatchManager::instance().set_session (this);
+ MIDI::Name::MidiPatchManager::instance().add_search_path (session_directory().midi_patch_path() );
ltc_tx_initialize();
/* initial program change will be delivered later; see ::config_changed() */
}
/* Now, finally, we can fill the playback buffers */
-
+
BootMessage (_("Filling playback buffers"));
-
+
boost::shared_ptr<RouteList> rl = routes.reader();
for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<Track> (*r);
Session::session_loaded ()
{
SessionLoaded();
-
+
_state_of_the_state = Clean;
-
+
DirtyChanged (); /* EMIT SIGNAL */
-
+
if (_is_new) {
save_state ("");
} else if (state_was_pending) {
remove_pending_capture_state ();
state_was_pending = false;
}
-
+
/* Now, finally, we can fill the playback buffers */
-
+
BootMessage (_("Filling playback buffers"));
force_locate (_transport_frame, false);
}
while (!feof (in)) {
size_t charsRead = fread (buf, sizeof(char), 1024, in);
-
+
if (ferror (in)) {
error << string_compose (_("Error reading session template file %1 (%2)"), in_path, strerror (errno)) << endmsg;
fclose (in);
std::string template_plugins = Glib::build_filename (session_template, X_("plugins"));
copy_recurse (template_plugins, plugins_dir ());
}
-
+
return 0;
} else {
}
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.
*/
-
+
set_session_range_location (0, 0);
-
+
/* 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);
}
_state_of_the_state = Clean;
- /* set up Master Out and Control Out if necessary */
+ /* set up Master Out and Monitor Out if necessary */
if (bus_profile) {
// Waves Tracks: always create master bus for Tracks
if (ARDOUR::Profile->get_trx() || bus_profile->master_out_channels) {
- boost::shared_ptr<Route> r (new Route (*this, _("Master"), Route::MasterOut, DataType::AUDIO));
+ boost::shared_ptr<Route> r (new Route (*this, _("Master"), PresentationInfo::MasterOut, DataType::AUDIO));
if (r->init ()) {
return -1;
}
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
- // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
-#endif
- {
+
+ 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);
}
if (!rl.empty()) {
- add_routes (rl, false, false, false);
+ add_routes (rl, false, false, false, PresentationInfo::max_order);
}
// Waves Tracks: Skip this. Always use autoconnection for Tracks
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);
bus_profile->output_ac = AutoConnectOption (0);
}
-
+
Config->set_input_auto_connect (bus_profile->input_ac);
Config->set_output_auto_connect (bus_profile->output_ac);
}
int
Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot, bool template_only)
{
+ DEBUG_TRACE (DEBUG::Locale, string_compose ("Session::save_state locale '%1'\n", setlocale (LC_NUMERIC, NULL)));
+
XMLTree tree;
std::string xml_path(_session_dir->root_path());
return 1;
}
+#ifndef NDEBUG
+ const int64_t save_start_time = g_get_monotonic_time();
+#endif
+
/* 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) {
SessionSaveUnderway (); /* EMIT SIGNAL */
+ bool mark_as_clean = true;
+
+ if (!snapshot_name.empty() && !switch_to_snapshot) {
+ mark_as_clean = false;
+ }
+
if (template_only) {
+ mark_as_clean = false;
tree.set_root (&get_template());
} else {
tree.set_root (&get_state());
if (snapshot_name.empty()) {
snapshot_name = _current_snapshot_name;
} else if (switch_to_snapshot) {
- _current_snapshot_name = snapshot_name;
- }
+ set_snapshot_name (snapshot_name);
+ }
+
+ assert (!snapshot_name.empty());
if (!pending) {
tmp_path = Glib::build_filename (tmp_path, legalize_for_path (snapshot_name) + temp_suffix);
cerr << "actually writing state to " << tmp_path << endl;
-
+
if (!tree.write (tmp_path)) {
error << string_compose (_("state could not be saved to %1"), tmp_path) << endmsg;
if (g_remove (tmp_path.c_str()) != 0) {
} else {
cerr << "renaming state to " << xml_path << endl;
-
+
if (::g_rename (tmp_path.c_str(), xml_path.c_str()) != 0) {
error << string_compose (_("could not rename temporary session file %1 to %2 (%3)"),
tmp_path, xml_path, g_strerror(errno)) << endmsg;
save_history (snapshot_name);
- bool was_dirty = dirty();
+ if (mark_as_clean) {
+ bool was_dirty = dirty();
- _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
+ _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
- if (was_dirty) {
- DirtyChanged (); /* EMIT SIGNAL */
+ if (was_dirty) {
+ DirtyChanged (); /* EMIT SIGNAL */
+ }
}
StateSaved (snapshot_name); /* EMIT SIGNAL */
}
+#ifndef NDEBUG
+ const int64_t elapsed_time_us = g_get_monotonic_time() - save_start_time;
+ cerr << "saved state in " << fixed << setprecision (1) << elapsed_time_us / 1000. << " ms\n";
+#endif
return 0;
}
return -1;
}
- XMLNode& root (*state_tree->root());
+ XMLNode const & root (*state_tree->root());
if (root.name() != X_("Session")) {
error << string_compose (_("Session file %1 is not a session"), xmlpath) << endmsg;
return -1;
}
- const XMLProperty* prop;
+ XMLProperty const * prop;
if ((prop = root.property ("version")) == 0) {
/* no version implies very old version of Ardour */
// only create a backup for a given statefile version once
if (!Glib::file_test (backup_path, Glib::FILE_TEST_EXISTS)) {
-
+
VersionMismatch (xmlpath, backup_path);
-
+
if (!copy_file (xmlpath, backup_path)) {;
return -1;
}
}
}
+ save_snapshot_name (snapshot_name);
+
return 0;
}
int
Session::load_options (const XMLNode& node)
{
- LocaleGuard lg (X_("C"));
+ LocaleGuard lg;
config.set_variables (node);
return 0;
}
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)
{
+ LocaleGuard lg;
XMLNode* node = new XMLNode("Session");
XMLNode* child;
snprintf(buf, sizeof(buf), "%d", CURRENT_SESSION_FILE_VERSION);
node->add_property("version", buf);
+ child = node->add_child ("ProgramVersion");
+ child->add_property("created-with", created_with);
+
+ std::string modified_with = string_compose ("%1 %2", PROGRAM_NAME, revision);
+ child->add_property("modified-with", modified_with);
+
/* store configuration settings */
if (full_state) {
node->add_property ("name", _name);
- snprintf (buf, sizeof (buf), "%" PRId64, _nominal_frame_rate);
+ snprintf (buf, sizeof (buf), "%" PRId64, _base_frame_rate);
node->add_property ("sample-rate", buf);
if (session_dirs.size() > 1) {
}
}
+ 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);
+ snprintf (buf, sizeof (buf), "%u", name_id_counter ());
+ node->add_property ("name-counter", buf);
+
/* save the event ID counter */
snprintf (buf, sizeof (buf), "%d", Evoral::event_id_counter());
node->add_property ("event-counter", buf);
+ /* save the VCA counter */
+
+ snprintf (buf, sizeof (buf), "%" PRIu32, VCA::get_next_vca_number());
+ node->add_property ("vca-counter", buf);
+
/* various options */
list<XMLNode*> midi_port_nodes = _midi_ports->get_midi_port_states();
node->add_child_nocopy (*midi_port_stuff);
}
- node->add_child_nocopy (config.get_variables ());
+ XMLNode& cfgxml (config.get_variables ());
+ if (!full_state) {
+ /* exclude search-paths from template */
+ cfgxml.remove_nodes_and_delete ("name", "audio-search-path");
+ cfgxml.remove_nodes_and_delete ("name", "midi-search-path");
+ cfgxml.remove_nodes_and_delete ("name", "raid-path");
+ }
+ node->add_child_nocopy (cfgxml);
node->add_child_nocopy (ARDOUR::SessionMetadata::Metadata()->get_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->add_property (X_("copy"), i->first->id().to_s());
+ can->add_property (X_("original"), i->second->id().to_s());
ca->add_child_nocopy (*can);
}
}
}
-
-
+
+
if (full_state) {
-
+
if (_locations) {
- node->add_child_nocopy (_locations->get_state());
+ node->add_child_nocopy (_locations->get_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();
-
+
if (ARDOUR::Profile->get_trx() && _locations) {
// For tracks we need stored the Auto Loop Range and all MIDI markers.
for (Locations::LocationList::const_iterator i = _locations->list ().begin (); i != _locations->list ().end (); ++i) {
}
}
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");
}
}
+ node->add_child_nocopy (_vca_manager->get_state());
+
child = node->add_child ("Routes");
{
boost::shared_ptr<RouteList> r = routes.reader ();
RoutePublicOrderSorter cmp;
RouteList public_order (*r);
public_order.sort (cmp);
-
- /* the sort should have put control outs first */
-
+
+ /* the sort should have put the monitor out first */
+
if (_monitor_out) {
assert (_monitor_out == public_order.front());
}
node->add_child_copy (*_extra_xml);
}
+ {
+ Glib::Threads::Mutex::Lock lm (lua_lock);
+ std::string saved;
+ {
+ luabridge::LuaRef savedstate ((*_lua_save)());
+ saved = savedstate.cast<std::string>();
+ }
+ lua.collect_garbage ();
+ lm.release ();
+
+ gchar* b64 = g_base64_encode ((const guchar*)saved.c_str (), saved.size ());
+ std::string b64s (b64);
+ g_free (b64);
+
+ XMLNode* script_node = new XMLNode (X_("Script"));
+ script_node->add_property (X_("lua"), LUA_VERSION);
+ script_node->add_content (b64s);
+ node->add_child_nocopy (*script_node);
+ }
+
return *node;
}
int
Session::set_state (const XMLNode& node, int version)
{
+ LocaleGuard lg;
XMLNodeList nlist;
XMLNode* child;
- const XMLProperty* prop;
+ XMLProperty const * prop;
int ret = -1;
_state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave);
if ((prop = node.property (X_("sample-rate"))) != 0) {
- _nominal_frame_rate = atoi (prop->value());
+ _base_frame_rate = atoi (prop->value());
+ _nominal_frame_rate = _base_frame_rate;
- if (_nominal_frame_rate != _current_frame_rate) {
- boost::optional<int> r = AskAboutSampleRateMismatch (_nominal_frame_rate, _current_frame_rate);
+ assert (AudioEngine::instance()->running ());
+ if (_base_frame_rate != AudioEngine::instance()->sample_rate ()) {
+ boost::optional<int> r = AskAboutSampleRateMismatch (_base_frame_rate, _current_frame_rate);
if (r.get_value_or (0)) {
goto out;
}
}
}
+ created_with = "unknown";
+ if ((child = find_named_node (node, "ProgramVersion")) != 0) {
+ if ((prop = child->property (X_("created-with"))) != 0) {
+ created_with = prop->value ();
+ }
+ }
+
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());
+ }
+
if ((prop = node.property (X_("id-counter"))) != 0) {
uint64_t x;
sscanf (prop->value().c_str(), "%" PRIu64, &x);
ID::init_counter (now);
}
- if ((prop = node.property (X_("event-counter"))) != 0) {
- Evoral::init_event_id_counter (atoi (prop->value()));
- }
+ if ((prop = node.property (X_("name-counter"))) != 0) {
+ init_name_id_counter (atoi (prop->value()));
+ }
+
+ if ((prop = node.property (X_("event-counter"))) != 0) {
+ Evoral::init_event_id_counter (atoi (prop->value()));
+ }
+ if ((prop = node.property (X_("vca-counter"))) != 0) {
+ uint32_t x;
+ sscanf (prop->value().c_str(), "%" PRIu32, &x);
+ VCA::set_next_vca_number (x);
+ } else {
+ VCA::set_next_vca_number (1);
+ }
if ((child = find_named_node (node, "MIDIPorts")) != 0) {
_midi_ports->set_midi_port_states (child->children());
}
}
+ if ((child = find_named_node (node, VCAManager::xml_node_name)) != 0) {
+ _vca_manager->set_state (*child, version);
+ }
+
if ((child = find_named_node (node, "Routes")) == 0) {
error << _("Session: XML state has no routes section") << endmsg;
goto out;
goto out;
}
+ /* Now that we have Routes and masters loaded, connect them if appropriate */
+
+ Slavable::Assign (_vca_manager); /* EMIT SIGNAL */
+
/* our diskstreams list is no longer needed as they are now all owned by their Route */
_diskstreams_2X.clear ();
ControlProtocolManager::instance().set_state (*child, version);
}
+ if ((child = find_named_node (node, "Script"))) {
+ for (XMLNodeList::const_iterator n = child->children ().begin (); n != child->children ().end (); ++n) {
+ if (!(*n)->is_content ()) { continue; }
+ gsize size;
+ guchar* buf = g_base64_decode ((*n)->content ().c_str (), &size);
+ try {
+ Glib::Threads::Mutex::Lock lm (lua_lock);
+ (*_lua_load)(std::string ((const char*)buf, size));
+ } catch (luabridge::LuaException const& e) {
+ cerr << "LuaException:" << e.what () << endl;
+ }
+ g_free (buf);
+ }
+ }
+
update_route_record_state ();
/* here beginneth the second phase ... */
+ set_snapshot_name (_current_snapshot_name);
StateReady (); /* EMIT SIGNAL */
BootMessage (_("Tracks/busses loaded; Adding to Session"));
- add_routes (new_routes, false, false, false);
+ add_routes (new_routes, false, false, false, PresentationInfo::max_order);
BootMessage (_("Finished adding tracks/busses"));
XMLNode* ds_child = find_named_node (node, X_("Diskstream"));
DataType type = DataType::AUDIO;
- const XMLProperty* prop = node.property("default-type");
+ XMLProperty const * prop = node.property("default-type");
if (prop) {
type = DataType (prop->value());
return ret;
}
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
- // boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
-#endif
+ BOOST_MARK_TRACK (track);
ret = track;
} else {
- enum Route::Flag flags = Route::Flag(0);
- const XMLProperty* prop = node.property("flags");
- if (prop) {
- flags = Route::Flag (string_2_enum (prop->value(), flags));
- }
-
+ PresentationInfo::Flag flags = PresentationInfo::get_flags (node);
boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML"), flags));
if (r->init () == 0 && r->set_state (node, version) == 0) {
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
- // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
-#endif
+ BOOST_MARK_ROUTE (r);
ret = r;
}
}
}
DataType type = DataType::AUDIO;
- const XMLProperty* prop = node.property("default-type");
+ XMLProperty const * prop = node.property("default-type");
if (prop) {
type = DataType (prop->value());
track->set_diskstream (*i);
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
- // boost_debug_shared_ptr_mark_interesting (track.get(), "Track");
-#endif
+ BOOST_MARK_TRACK (track);
ret = track;
} else {
- enum Route::Flag flags = Route::Flag(0);
- const XMLProperty* prop = node.property("flags");
- if (prop) {
- flags = Route::Flag (string_2_enum (prop->value(), flags));
- }
-
+ PresentationInfo::Flag flags = PresentationInfo::get_flags (node);
boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML"), flags));
if (r->init () == 0 && r->set_state (node, version) == 0) {
-#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
- // boost_debug_shared_ptr_mark_interesting (r.get(), "Route");
-#endif
+ BOOST_MARK_ROUTE (r);
ret = r;
}
}
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
if ((region = XMLRegionFactory (**niter, false)) == 0) {
error << _("Session: cannot create Region from XML description.");
- const XMLProperty *name = (**niter).property("name");
+ XMLProperty const * name = (**niter).property("name");
if (name) {
error << " " << string_compose (_("Can not load state for region '%1'"), name->value());
{
XMLNodeList calist = node.children();
XMLNodeConstIterator caiter;
- XMLProperty *caprop;
+ XMLProperty const * caprop;
for (caiter = calist.begin(); caiter != calist.end(); ++caiter) {
XMLNode* ca = *caiter;
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
if ((*niter)->name() == "Source") {
- /* it may already exist, so don't recreate it unnecessarily
+ /* it may already exist, so don't recreate it unnecessarily
*/
- XMLProperty* prop = (*niter)->property (X_("id"));
+ XMLProperty const * prop = (*niter)->property (X_("id"));
if (!prop) {
error << _("Nested source has no ID info in session file! (ignored)") << endmsg;
continue;
boost::shared_ptr<Region>
Session::XMLRegionFactory (const XMLNode& node, bool full)
{
- const XMLProperty* type = node.property("type");
+ XMLProperty const * type = node.property("type");
try {
boost::shared_ptr<AudioRegion>
Session::XMLAudioRegionFactory (const XMLNode& node, bool /*full*/)
{
- const XMLProperty* prop;
+ XMLProperty const * prop;
boost::shared_ptr<Source> source;
boost::shared_ptr<AudioSource> as;
SourceList sources;
boost::shared_ptr<MidiRegion>
Session::XMLMidiRegionFactory (const XMLNode& node, bool /*full*/)
{
- const XMLProperty* prop;
+ XMLProperty const * prop;
boost::shared_ptr<Source> source;
boost::shared_ptr<MidiSource> ms;
SourceList sources;
{
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) {
- retry:
+#ifdef PLATFORM_WINDOWS
+ int old_mode = 0;
+#endif
+
+ XMLNode srcnode (**niter);
+ bool try_replace_abspath = true;
+
+retry:
try {
- if ((source = XMLSourceFactory (**niter)) == 0) {
+#ifdef PLATFORM_WINDOWS
+ // do not show "insert media" popups (files embedded from removable media).
+ old_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
+#endif
+ if ((source = XMLSourceFactory (srcnode)) == 0) {
error << _("Session: cannot create Source from XML description.") << endmsg;
}
+#ifdef PLATFORM_WINDOWS
+ SetErrorMode(old_mode);
+#endif
} catch (MissingSource& err) {
+#ifdef PLATFORM_WINDOWS
+ 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<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;
+ 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;
- }
+ }
}
}
}
int
-Session::save_template (string template_name)
+Session::save_template (string template_name, bool replace_existing)
{
if ((_state_of_the_state & CannotSave) || template_name.empty ()) {
return -1;
}
if (!ARDOUR::Profile->get_trx()) {
- if (Glib::file_test (template_dir_path, Glib::FILE_TEST_EXISTS)) {
+ if (!replace_existing && Glib::file_test (template_dir_path, Glib::FILE_TEST_EXISTS)) {
warning << string_compose(_("Template \"%1\" already exists - new version not created"),
template_dir_path) << endmsg;
- return -1;
+ return -2;
}
-
+
if (g_mkdir_with_parents (template_dir_path.c_str(), 0755) != 0) {
error << string_compose(_("Could not create directory for Session template\"%1\" (%2)"),
template_dir_path, g_strerror (errno)) << endmsg;
/* file to write */
std::string template_file_path;
-
+
if (ARDOUR::Profile->get_trx()) {
template_file_path = template_name;
} else {
}
SessionSaveUnderway (); /* EMIT SIGNAL */
-
+
XMLTree tree;
- tree.set_root (&get_template());
+ {
+ PBD::Unwinder<std::string> uw (_template_state_dir, template_dir_path);
+ tree.set_root (&get_template());
+ }
+
if (!tree.write (template_file_path)) {
error << _("template not saved") << endmsg;
return -1;
}
- if (!ARDOUR::Profile->get_trx()) {
- /* copy plugin state directory */
-
- std::string template_plugin_state_path (Glib::build_filename (template_dir_path, X_("plugins")));
-
- if (g_mkdir_with_parents (template_plugin_state_path.c_str(), 0755) != 0) {
- error << string_compose(_("Could not create directory for Session template plugin state\"%1\" (%2)"),
- template_plugin_state_path, g_strerror (errno)) << endmsg;
- return -1;
- }
- copy_files (plugins_dir(), template_plugin_state_path);
- }
-
store_recent_templates (template_file_path);
return 0;
void
Session::refresh_disk_space ()
{
-#if __APPLE__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
-
+#if __APPLE__ || __FreeBSD__ || __NetBSD__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
+
Glib::Threads::Mutex::Lock lm (space_lock);
/* get freespace on every FS that is part of the session path */
_total_free_4k_blocks_uncertain = false;
for (vector<space_and_path>::iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+#if defined(__NetBSD__)
+ struct statvfs statfsbuf;
+ statvfs (i->path.c_str(), &statfsbuf);
+#else
struct statfs statfsbuf;
- statfs (i->path.c_str(), &statfsbuf);
+ statfs (i->path.c_str(), &statfsbuf);
+#endif
double const scale = statfsbuf.f_bsize / 4096.0;
/* See if this filesystem is read-only */
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)
{
cmd->name ()));
_current_trans->add_command (cmd);
}
+
+PBD::StatefulDiffCommand*
+Session::add_stateful_diff_command (boost::shared_ptr<PBD::StatefulDestructible> sfd)
+{
+ PBD::StatefulDiffCommand* cmd = new PBD::StatefulDiffCommand (sfd);
+ add_command (cmd);
+ return cmd;
+}
+
void
Session::begin_reversible_command (const string& name)
{
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
- XMLProperty* prop;
+ XMLProperty const * prop;
if ((prop = (*niter)->property (X_("type"))) == 0) {
continue;
return 0;
}
- this_snapshot_path = _path;
- this_snapshot_path += legalize_for_path (_current_snapshot_name);
+ this_snapshot_path = Glib::build_filename (_path, legalize_for_path (_current_snapshot_name));
this_snapshot_path += statefile_suffix;
for (vector<string>::iterator i = state_files.begin(); i != state_files.end(); ++i) {
+ cerr << "Looking at snapshot " << (*i) << " ( with this = [" << this_snapshot_path << "])\n";
+
if (exclude_this_snapshot && *i == this_snapshot_path) {
+ cerr << "\texcluded\n";
continue;
+
}
if (find_all_sources (*i, result) < 0) {
bool removed = false;
const RegionFactory::RegionMap& regions (RegionFactory::regions());
- for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end();) {
uint32_t used = playlists->region_use_count (i->second);
if (used == 0 && !i->second->automatic ()) {
+ boost::weak_ptr<Region> w = i->second;
+ ++i;
removed = true;
- RegionFactory::map_remove (i->second);
+ RegionFactory::map_remove (w);
+ } else {
+ ++i;
}
}
if (removed) {
// re-check to remove parent references of compound regions
- for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+ for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end();) {
if (!(i->second->whole_file() && i->second->max_source_level() > 0)) {
+ ++i;
continue;
}
assert(boost::dynamic_pointer_cast<PlaylistSource>(i->second->source (0)) != 0);
if (0 == playlists->region_use_count (i->second)) {
- RegionFactory::map_remove (i->second);
+ boost::weak_ptr<Region> w = i->second;
+ ++i;
+ RegionFactory::map_remove (w);
+ } else {
+ ++i;
}
}
}
save_state ("");
}
+bool
+Session::can_cleanup_peakfiles () const
+{
+ if (deletion_in_progress()) {
+ return false;
+ }
+ if (!_writable || (_state_of_the_state & CannotSave)) {
+ warning << _("Cannot cleanup peak-files for read-only session.") << endmsg;
+ return false;
+ }
+ if (record_status() == Recording) {
+ error << _("Cannot cleanup peak-files while recording") << endmsg;
+ return false;
+ }
+ return true;
+}
+
int
-Session::cleanup_sources (CleanupReport& rep)
+Session::cleanup_peakfiles ()
{
- // FIXME: needs adaptation to midi
+ Glib::Threads::Mutex::Lock lm (peak_cleanup_lock, Glib::Threads::TRY_LOCK);
+ if (!lm.locked()) {
+ return -1;
+ }
- vector<boost::shared_ptr<Source> > dead_sources;
- string audio_path;
- string midi_path;
- vector<string> candidates;
- vector<string> unused;
- set<string> all_sources;
- bool used;
- string spath;
- int ret = -1;
- string tmppath1;
- string tmppath2;
- Searchpath asp;
- Searchpath msp;
+ assert (can_cleanup_peakfiles ());
+ assert (!peaks_cleanup_in_progres());
- _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
+ _state_of_the_state = StateOfTheState (_state_of_the_state | PeakCleanup);
- /* consider deleting all unused playlists */
+ int timeout = 5000; // 5 seconds
+ while (!SourceFactory::files_with_peaks.empty()) {
+ Glib::usleep (1000);
+ if (--timeout < 0) {
+ warning << _("Timeout waiting for peak-file creation to terminate before cleanup, please try again later.") << endmsg;
+ _state_of_the_state = StateOfTheState (_state_of_the_state & (~PeakCleanup));
+ return -1;
+ }
+ }
- if (playlists->maybe_delete_unused (boost::bind (Session::ask_about_playlist_deletion, _1))) {
- ret = 0;
+ for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+ boost::shared_ptr<AudioSource> as;
+ if ((as = boost::dynamic_pointer_cast<AudioSource> (i->second)) != 0) {
+ as->close_peakfile();
+ }
+ }
+
+ PBD::clear_directory (session_directory().peak_path());
+
+ _state_of_the_state = StateOfTheState (_state_of_the_state & (~PeakCleanup));
+
+ for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+ boost::shared_ptr<AudioSource> as;
+ if ((as = boost::dynamic_pointer_cast<AudioSource> (i->second)) != 0) {
+ SourceFactory::setup_peakfile(as, true);
+ }
+ }
+ return 0;
+}
+
+static void
+merge_all_sources (boost::shared_ptr<const Playlist> pl, std::set<boost::shared_ptr<Source> >* all_sources)
+{
+ pl->deep_sources (*all_sources);
+}
+
+int
+Session::cleanup_sources (CleanupReport& rep)
+{
+ // FIXME: needs adaptation to midi
+
+ vector<boost::shared_ptr<Source> > dead_sources;
+ string audio_path;
+ string midi_path;
+ vector<string> candidates;
+ vector<string> unused;
+ set<string> sources_used_by_all_snapshots;
+ string spath;
+ int ret = -1;
+ string tmppath1;
+ string tmppath2;
+ Searchpath asp;
+ Searchpath msp;
+ set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
+
+ _state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
+
+ /* this is mostly for windows which doesn't allow file
+ * renaming if the file is in use. But we don't special
+ * case it because we need to know if this causes
+ * problems, and the easiest way to notice that is to
+ * keep it in place for all platforms.
+ */
+
+ request_stop (false);
+ _butler->summon ();
+ _butler->wait_until_finished ();
+
+ /* consider deleting all unused playlists */
+
+ if (playlists->maybe_delete_unused (boost::bind (Session::ask_about_playlist_deletion, _1))) {
+ ret = 0;
goto out;
}
capture files.
*/
- if (!i->second->used() && (i->second->length(i->second->timeline_position() > 0))) {
+ if (!i->second->used() && (i->second->length(i->second->timeline_position()) > 0)) {
dead_sources.push_back (i->second);
i->second->drop_references ();
}
find_files_matching_filter (candidates, audio_path, accept_all_audio_files, (void *) 0, true, true);
find_files_matching_filter (candidates, midi_path, accept_all_midi_files, (void *) 0, true, true);
- /* find all sources, but don't use this snapshot because the
- state file on disk still references sources we may have already
- dropped.
+ /* 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.
*/
- find_all_sources_across_snapshots (all_sources, true);
+ find_all_sources_across_snapshots (sources_used_by_all_snapshots, true);
+
+ /* Although the region factory has a list of all regions ever created
+ * for this session, we're only interested in regions actually in
+ * playlists right now. So merge all playlist regions lists together.
+ *
+ * This will include the playlists used within compound regions.
+ */
+
+ playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot));
/* add our current source list
*/
SourceMap::iterator tmp = i;
++tmp;
- if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) != 0) {
+ if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) == 0) {
+ /* not a file */
+ i = tmp;
+ continue;
+ }
- if (!fs->is_stub()) {
+ /* this is mostly for windows which doesn't allow file
+ * renaming if the file is in use. But we do not special
+ * case it because we need to know if this causes
+ * problems, and the easiest way to notice that is to
+ * keep it in place for all platforms.
+ */
- if (playlists->source_use_count (fs) != 0) {
- all_sources.insert (fs->path());
- } else {
-
- /* we might not remove this source from disk, because it may be used
- by other snapshots, but its not being used in this version
- so lets get rid of it now, along with any representative regions
- in the region list.
- */
-
- RegionFactory::remove_regions_using_source (i->second);
-
- // also remove source from all_sources
-
- for (set<string>::iterator j = all_sources.begin(); j != all_sources.end(); ++j) {
- spath = Glib::path_get_basename (*j);
- if (spath == i->second->name()) {
- all_sources.erase (j);
- break;
- }
- }
+ fs->close ();
- sources.erase (i);
- }
+ if (!fs->is_stub()) {
+
+ /* Note that we're checking a list of all
+ * sources across all snapshots with the list
+ * of sources used by this snapshot.
+ */
+
+ if (sources_used_by_this_snapshot.find (i->second) != sources_used_by_this_snapshot.end()) {
+ /* this source is in use by this snapshot */
+ sources_used_by_all_snapshots.insert (fs->path());
+ 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
+ */
+
+ /* remove all related regions from RegionFactory master list
+ */
+
+ RegionFactory::remove_regions_using_source (i->second);
+
+ /* remove from our current source list
+ * also. We may not remove it from
+ * disk, because it may be used by
+ * other snapshots, but it isn't used inside this
+ * snapshot anymore, so we don't need a
+ * reference to it.
+ */
+
+ sources.erase (i);
}
}
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".
+ */
+
+ cerr << "Candidates: " << candidates.size() << endl;
+ cerr << "Used by others: " << sources_used_by_all_snapshots.size() << endl;
+
for (vector<string>::iterator x = candidates.begin(); x != candidates.end(); ++x) {
- used = false;
+ bool used = false;
spath = *x;
- for (set<string>::iterator i = all_sources.begin(); i != all_sources.end(); ++i) {
+ for (set<string>::iterator i = sources_used_by_all_snapshots.begin(); i != sources_used_by_all_snapshots.end(); ++i) {
tmppath1 = canonical_path (spath);
tmppath2 = canonical_path ((*i));
+ cerr << "\t => " << tmppath2 << endl;
+
if (tmppath1 == tmppath2) {
used = true;
break;
}
}
+ cerr << "Actually unused: " << unused.size() << endl;
+
+ if (unused.empty()) {
+ /* Nothing to do */
+ ret = 0;
+ goto out;
+ }
+
/* now try to move all unused files into the "dead" directory(ies) */
for (vector<string>::iterator x = unused.begin(); x != unused.end(); ++x) {
- struct stat statbuf;
+ GStatBuf statbuf;
string newpath;
newpath = newpath_v;
}
- } else {
-
- /* it doesn't exist, or we can't read it or something */
-
}
- stat ((*x).c_str(), &statbuf);
-
- if (::rename ((*x).c_str(), newpath.c_str()) != 0) {
- error << string_compose (_("cannot rename unused file source from %1 to %2 (%3)"),
- (*x), newpath, strerror (errno))
- << endmsg;
- goto out;
+ 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;
+ continue;
}
/* 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
- */
+ 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
+ */
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, strerror (errno))
- << endmsg;
+ 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;
/* try to back out */
- ::rename (newpath.c_str(), _path.c_str());
+ ::g_rename (newpath.c_str (), _path.c_str ());
goto out;
}
}
rep.paths.push_back (*x);
- rep.space += statbuf.st_size;
- }
+ rep.space += statbuf.st_size;
+ }
/* dump the history list */
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 ()
{
_state_of_the_state = Clean;
-
if (was_dirty) {
DirtyChanged(); /* EMIT SIGNAL */
}
Session::controllable_by_descriptor (const ControllableDescriptor& desc)
{
boost::shared_ptr<Controllable> c;
+ boost::shared_ptr<Stripable> s;
boost::shared_ptr<Route> r;
switch (desc.top_level_type()) {
case ControllableDescriptor::NamedRoute:
{
std::string str = desc.top_level_name();
+
if (str == "Master" || str == "master") {
- r = _master_out;
- } else if (str == "control" || str == "listen") {
- r = _monitor_out;
+ s = _master_out;
+ } else if (str == "control" || str == "listen" || str == "monitor" || str == "Monitor") {
+ s = _monitor_out;
+ } else if (str == "auditioner") {
+ s = auditioner;
} else {
- r = route_by_name (desc.top_level_name());
+ s = route_by_name (desc.top_level_name());
}
+
break;
}
- case ControllableDescriptor::RemoteControlID:
- r = route_by_remote_id (desc.rid());
+ case ControllableDescriptor::PresentationOrderRoute:
+ s = get_remote_nth_stripable (desc.presentation_order(), PresentationInfo::Route);
+ break;
+
+ case ControllableDescriptor::PresentationOrderTrack:
+ s = get_remote_nth_stripable (desc.presentation_order(), PresentationInfo::Track);
+ break;
+
+ case ControllableDescriptor::PresentationOrderBus:
+ s = get_remote_nth_stripable (desc.presentation_order(), PresentationInfo::Bus);
+ break;
+
+ case ControllableDescriptor::PresentationOrderVCA:
+ s = get_remote_nth_stripable (desc.presentation_order(), PresentationInfo::VCA);
+ break;
+
+ case ControllableDescriptor::SelectionCount:
+ s = route_by_selected_count (desc.selection_id());
break;
}
- if (!r) {
+ if (!s) {
return c;
}
+ r = boost::dynamic_pointer_cast<Route> (s);
+
switch (desc.subtype()) {
case ControllableDescriptor::Gain:
- c = r->gain_control ();
+ c = s->gain_control ();
break;
case ControllableDescriptor::Trim:
- c = r->trim()->gain_control ();
+ c = s->trim_control ();
break;
case ControllableDescriptor::Solo:
- c = r->solo_control();
+ c = s->solo_control();
break;
case ControllableDescriptor::Mute:
- c = r->mute_control();
+ c = s->mute_control();
break;
case ControllableDescriptor::Recenable:
- {
- boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(r);
-
- if (t) {
- c = t->rec_enable_control ();
- }
+ c = s->rec_enable_control ();
break;
- }
case ControllableDescriptor::PanDirection:
- {
- c = r->pannable()->pan_azimuth_control;
+ c = s->pan_azimuth_control();
break;
- }
case ControllableDescriptor::PanWidth:
- {
- c = r->pannable()->pan_width_control;
+ c = s->pan_width_control();
break;
- }
case ControllableDescriptor::PanElevation:
- {
- c = r->pannable()->pan_elevation_control;
+ c = s->pan_elevation_control();
break;
- }
case ControllableDescriptor::Balance:
/* XXX simple pan control */
--parameter_index;
}
+ if (!r) {
+ return c;
+ }
+
boost::shared_ptr<Processor> p = r->nth_plugin (plugin);
if (p) {
break;
}
- case ControllableDescriptor::SendGain:
- {
+ case ControllableDescriptor::SendGain: {
uint32_t send = desc.target (0);
-
- /* revert to zero-based counting */
-
if (send > 0) {
--send;
}
-
- boost::shared_ptr<Processor> p = r->nth_send (send);
-
- if (p) {
- boost::shared_ptr<Send> s = boost::dynamic_pointer_cast<Send>(p);
- boost::shared_ptr<Amp> a = s->amp();
-
- if (a) {
- c = s->amp()->gain_control();
- }
+ if (!r) {
+ return c;
}
+ c = r->send_level_controllable (send);
break;
}
XMLNode*
Session::instant_xml (const string& node_name)
{
+#ifdef MIXBUS // "Safe Mode" (shift + click open) -> also ignore instant.xml
+ if (get_disable_all_loaded_plugins ()) {
+ return NULL;
+ }
+#endif
return Stateful::instant_xml (node_name, _path);
}
return 0;
}
- if (!Config->get_save_history() || Config->get_saved_history_depth() < 0 ||
+ if (!Config->get_save_history() || Config->get_saved_history_depth() < 0 ||
(_history.undo_depth() == 0 && _history.redo_depth() == 0)) {
return 0;
}
// replace history
_history.clear();
- for (XMLNodeConstIterator it = tree.root()->children().begin(); it != tree.root()->children().end(); it++) {
+ for (XMLNodeConstIterator it = tree.root()->children().begin(); it != tree.root()->children().end(); ++it) {
XMLNode *t = *it;
UndoTransaction* ut = new UndoTransaction ();
} 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") {
_clicking = false;
}
+ } else if (p == "click-record-only") {
+
+ _click_rec_only = Config->get_click_record_only();
+
} else if (p == "click-gain") {
-
+
if (_click_gain) {
- _click_gain->set_gain (Config->get_click_gain(), this);
+ _click_gain->gain_control()->set_value (Config->get_click_gain(), Controllable::NoGroup);
}
} else if (p == "send-mtc") {
_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 ();
} else if (p == "solo-control-is-listen-control") {
solo_control_mode_changed ();
} else if (p == "solo-mute-gain") {
- _solo_cut_control->Changed();
+ _solo_cut_control->Changed (true, Controllable::NoGroup);
} else if (p == "timecode-offset" || p == "timecode-offset-negative") {
last_timecode_valid = false;
} else if (p == "playback-buffer-seconds") {
Session::setup_midi_machine_control ()
{
_mmc = new MIDI::MachineControl;
- _mmc->set_ports (_midi_ports->mmc_input_port(), _midi_ports->mmc_output_port());
+
+ boost::shared_ptr<AsyncMIDIPort> async_in = boost::dynamic_pointer_cast<AsyncMIDIPort> (_midi_ports->mmc_input_port());
+ boost::shared_ptr<AsyncMIDIPort> async_out = boost::dynamic_pointer_cast<AsyncMIDIPort> (_midi_ports->mmc_output_port());
+
+ if (!async_out || !async_out) {
+ return;
+ }
+
+ /* XXXX argh, passing raw pointers back into libmidi++ */
+
+ MIDI::Port* mmc_in = async_in.get();
+ MIDI::Port* mmc_out = async_out.get();
+
+ _mmc->set_ports (mmc_in, mmc_out);
_mmc->Play.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
_mmc->DeferredPlay.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
return _solo_cut_control;
}
+void
+Session::save_snapshot_name (const std::string & n)
+{
+ /* assure Stateful::_instant_xml is loaded
+ * add_instant_xml() only adds to existing data and defaults
+ * to use an empty Tree otherwise
+ */
+ instant_xml ("LastUsedSnapshot");
+
+ XMLNode* last_used_snapshot = new XMLNode ("LastUsedSnapshot");
+ last_used_snapshot->add_property ("name", string(n));
+ add_instant_xml (*last_used_snapshot, false);
+}
+
+void
+Session::set_snapshot_name (const std::string & n)
+{
+ _current_snapshot_name = n;
+ save_snapshot_name (n);
+}
+
int
Session::rename (const std::string& new_name)
{
* interchange subdirectory
* session file
* session history
-
+
* Backup files are left unchanged and not renamed.
*/
fs->close ();
}
}
-
+
/* pass one: not 100% safe check that the new directory names don't
* already exist ...
*/
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
-
+
oldstr = (*i).path;
-
+
/* this is a stupid hack because Glib::path_get_dirname() is
* lexical-only, and so passing it /a/b/c/ gives a different
* result than passing it /a/b/c ...
*/
-
+
if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
oldstr = oldstr.substr (0, oldstr.length() - 1);
}
-
+
string base = Glib::path_get_dirname (oldstr);
-
+
newstr = Glib::build_filename (base, legal_name);
-
+
cerr << "Looking for " << newstr << endl;
-
+
if (Glib::file_test (newstr, Glib::FILE_TEST_EXISTS)) {
cerr << " exists\n";
return -1;
/* Session dirs */
first = true;
-
+
for (vector<space_and_path>::iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
vector<string> v;
oldstr = (*i).path;
-
+
/* this is a stupid hack because Glib::path_get_dirname() is
* lexical-only, and so passing it /a/b/c/ gives a different
* result than passing it /a/b/c ...
*/
-
+
if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
oldstr = oldstr.substr (0, oldstr.length() - 1);
}
newstr = Glib::build_filename (base, legal_name);
cerr << "for " << oldstr << " new dir = " << newstr << endl;
-
- cerr << "Rename " << oldstr << " => " << newstr << endl;
+
+ cerr << "Rename " << oldstr << " => " << newstr << endl;
if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
cerr << string_compose (_("renaming %s as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
error << string_compose (_("renaming %s as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
}
/* Reset path in "session dirs" */
-
+
(*i).path = newstr;
(*i).blocks = 0;
-
+
/* reset primary SessionDirectory object */
-
+
if (first) {
(*_session_dir) = newstr;
new_path = newstr;
string new_interchange_dir;
/* use newstr here because we renamed the path
- * (folder/directory) that used to be oldstr to newstr above
- */
-
- v.push_back (newstr);
+ * (folder/directory) that used to be oldstr to newstr above
+ */
+
+ v.push_back (newstr);
v.push_back (interchange_dir_name);
v.push_back (Glib::path_get_basename (oldstr));
v.push_back (newstr);
v.push_back (interchange_dir_name);
v.push_back (legal_name);
-
+
new_interchange_dir = Glib::build_filename (v);
-
+
cerr << "Rename " << old_interchange_dir << " => " << new_interchange_dir << endl;
-
+
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,
}
/* state file */
-
+
oldstr = Glib::build_filename (new_path, _current_snapshot_name + statefile_suffix);
newstr= Glib::build_filename (new_path, legal_name + statefile_suffix);
-
- cerr << "Rename " << oldstr << " => " << newstr << endl;
+
+ cerr << "Rename " << oldstr << " => " << newstr << endl;
if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
cerr << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
}
/* history file */
-
+
oldstr = Glib::build_filename (new_path, _current_snapshot_name) + history_suffix;
if (Glib::file_test (oldstr, Glib::FILE_TEST_EXISTS)) {
newstr = Glib::build_filename (new_path, legal_name) + history_suffix;
-
- cerr << "Rename " << oldstr << " => " << newstr << endl;
-
+
+ cerr << "Rename " << oldstr << " => " << newstr << endl;
+
if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
cerr << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
/* remove old name from recent sessions */
remove_recent_sessions (_path);
_path = new_path;
-
+
/* update file source paths */
-
+
for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
if (fs) {
}
}
- _current_snapshot_name = new_name;
+ set_snapshot_name (new_name);
_name = new_name;
-
+
set_dirty ();
/* save state again to get everything just right */
}
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;
-}
+ if (doc == NULL) {
+ xmlFreeParserCtxt(ctxt);
+ return -1;
+ }
-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;
+ xmlNodePtr node = xmlDocGetRootElement(doc);
- if (get_session_info_from_path (tree, xmlpath)) {
+ if (node == NULL) {
+ xmlFreeParserCtxt(ctxt);
+ xmlFreeDoc (doc);
return -1;
}
/* sample rate */
- const XMLProperty* prop;
- if ((prop = tree.root()->property (X_("sample-rate"))) != 0) {
- sample_rate = atoi (prop->value());
- found_sr = true;
- }
+ 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);
- const XMLNodeList& children (tree.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) {
- const XMLNode* option = *oc;
- const XMLProperty* name = option->property("name");
+ return !(found_sr && found_data_format); // zero if they are both found
+}
- if (!name) {
- continue;
- }
+std::string
+Session::get_snapshot_from_instant (const std::string& session_dir)
+{
+ std::string instant_xml_path = Glib::build_filename (session_dir, "instant.xml");
- if (name->value() == "native-file-data-format") {
- const XMLProperty* 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;
- }
+ if (!Glib::file_test (instant_xml_path, Glib::FILE_TEST_EXISTS)) {
+ return "";
}
- return !(found_sr && found_data_format); // zero if they are both found
+ XMLTree tree;
+ if (!tree.read (instant_xml_path)) {
+ return "";
+ }
+
+ XMLProperty const * prop;
+ XMLNode *last_used_snapshot = tree.root()->child("LastUsedSnapshot");
+ if (last_used_snapshot && (prop = last_used_snapshot->property ("name")) != 0) {
+ return prop->value();
+ }
+
+ return "";
}
typedef std::vector<boost::shared_ptr<FileSource> > SeveralFileSources;
{
Glib::Threads::Mutex::Lock lm (source_lock);
-
+
cerr << " total sources = " << sources.size();
-
+
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
-
+
if (!fs) {
continue;
}
-
+
if (fs->within_session()) {
continue;
}
-
+
if (source_path_map.find (fs->path()) != source_path_map.end()) {
source_path_map[fs->path()].push_back (fs);
} else {
v.push_back (fs);
source_path_map.insert (make_pair (fs->path(), v));
}
-
+
total++;
}
-
+
cerr << " fsources = " << total << endl;
-
+
for (SourcePathMap::iterator i = source_path_map.begin(); i != source_path_map.end(); ++i) {
-
+
/* tell caller where we are */
-
+
string old_path = i->first;
-
+
callback (n, total, old_path);
-
+
cerr << old_path << endl;
-
+
new_path.clear ();
-
+
switch (i->second.front()->type()) {
case DataType::AUDIO:
new_path = new_audio_source_path_for_embedded (old_path);
break;
-
+
case DataType::MIDI:
/* XXX not implemented yet */
break;
}
-
+
if (new_path.empty()) {
continue;
}
-
+
cerr << "Move " << old_path << " => " << new_path << endl;
-
+
if (!copy_file (old_path, new_path)) {
cerr << "failed !\n";
ret = -1;
}
-
+
/* make sure we stop looking in the external
dir/folder. Remember, this is an all-or-nothing
operations, it doesn't merge just some files.
v.push_back (new_session_path); /* just one directory/folder */
v.push_back (typedir);
v.push_back (Glib::path_get_basename (old_path));
-
+
return Glib::build_filename (v);
}
/* get total size */
for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
-
+
/* need to clear this because
* find_files_matching_filter() is cumulative
*/
-
+
files.clear ();
-
+
find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
-
+
all += files.size();
for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
}
/* save old values so we can switch back if we are not switching to the new session */
-
+
string old_path = _path;
string old_name = _name;
string old_snapshot = _current_snapshot_name;
old_search_path[DataType::AUDIO] = source_search_path (DataType::AUDIO);
old_search_path[DataType::MIDI] = source_search_path (DataType::MIDI);
- old_config_search_path[DataType::AUDIO] = config.get_audio_search_path ();
- old_config_search_path[DataType::MIDI] = config.get_midi_search_path ();
+ old_config_search_path[DataType::AUDIO] = config.get_audio_search_path ();
+ old_config_search_path[DataType::MIDI] = config.get_midi_search_path ();
/* switch session directory */
-
+
(*_session_dir) = to_dir;
/* create new tree */
-
+
if (!_session_dir->create()) {
saveas.failure_message = string_compose (_("Cannot create new session folder %1"), to_dir);
return -1;
/* copy all relevant files. Find each location in session_dirs,
* and copy files from there to target.
*/
-
+
for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
-
+
/* need to clear this because
* find_files_matching_filter() is cumulative
*/
-
+
files.clear ();
-
+
const size_t prefix_len = (*sd).path.size();
-
+
/* Work just on the files within this session dir */
-
+
find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
-
+
/* add dir separator to protect against collisions with
* track names (e.g. track named "audiofiles" or
* "analysis".
folder. That really was a bad idea, but I'm not fixing it as part of
implementing ::save_as().
*/
-
+
for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
std::string from = *i;
continue;
}
#endif
-
+
if (from.find (audiofile_dir_string) != string::npos) {
-
+
/* audio file: only copy if asked */
if (saveas.include_media && saveas.copy_media) {
-
+
string to = make_new_media_path (*i, to_dir, new_folder);
info << "media file copying from " << from << " to " << to << endmsg;
-
+
if (!copy_file (from, to)) {
throw Glib::FileError (Glib::FileError::IO_ERROR,
string_compose(_("\ncopying \"%1\" failed !"), from));
}
}
-
+
/* we found media files inside the session folder */
-
+
internal_file_cnt++;
} else if (from.find (midifile_dir_string) != string::npos) {
*/
if (saveas.include_media) {
-
+
string to = make_new_media_path (*i, to_dir, new_folder);
-
+
info << "media file copying from " << from << " to " << to << endmsg;
-
+
if (!copy_file (from, to)) {
throw Glib::FileError (Glib::FileError::IO_ERROR, "copy failed");
}
}
/* we found media files inside the session folder */
-
+
internal_file_cnt++;
-
+
} else if (from.find (analysis_dir_string) != string::npos) {
/* make sure analysis dir exists in
* new session folder, but we're not
* copying analysis files here, see
- * below
+ * below
*/
-
+
(void) g_mkdir_with_parents (analysis_dir().c_str(), 775);
continue;
-
+
} else {
-
+
/* normal non-media file. Don't copy state, history, etc.
*/
-
+
bool do_copy = true;
-
+
for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
/* end of filename matches extension, do not copy file */
do_copy = false;
break;
- }
+ }
}
if (!saveas.copy_media && from.find (peakfile_suffix) != string::npos) {
*/
do_copy = false;
}
-
+
if (do_copy) {
string to = Glib::build_filename (to_dir, from.substr (prefix_len));
-
+
info << "attempting to make directory/folder " << to << endmsg;
if (g_mkdir_with_parents (Glib::path_get_dirname (to).c_str(), 0755)) {
}
info << "attempting to copy " << from << " to " << to << endmsg;
-
+
if (!copy_file (from, to)) {
throw Glib::FileError (Glib::FileError::IO_ERROR,
string_compose(_("\ncopying \"%1\" failed !"), from));
}
}
}
-
+
/* measure file size even if we're not going to copy so that our Progress
signals are correct, since we included these do-not-copy files
in the computation of the total size and file count.
*/
-
+
GStatBuf gsb;
g_stat (from.c_str(), &gsb);
copied += gsb.st_size;
cnt++;
-
+
double fraction = (double) copied / total_bytes;
-
+
bool keep_going = true;
if (saveas.copy_media) {
* media is not being copied, because
* it will be fast(ish).
*/
-
+
/* tell someone "X percent, file M of N"; M is one-based */
-
+
boost::optional<bool> res = saveas.Progress (fraction, cnt, all);
-
+
if (res) {
keep_going = *res;
}
}
if (saveas.include_media) {
-
+
if (saveas.copy_media) {
#ifndef PLATFORM_WINDOWS
/* There are problems with analysis files on
*
* This is a tricky problem to solve so for
* just don't copy these files. They will be
- * regenerated as-needed anyway, subject to the
+ * regenerated as-needed anyway, subject to the
* existing issue that the filenames will be
* rejected by Windows, which is a separate
* problem (though related).
/* only needed if we are copying media, since the
* analysis data refers to media data
*/
-
+
old = analysis_dir ();
if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
string newdir = Glib::build_filename (to_dir, "analysis");
copy_files (old, newdir);
}
-#endif /* PLATFORM_WINDOWS */
+#endif /* PLATFORM_WINDOWS */
}
}
-
-
+
_path = to_dir;
- _current_snapshot_name = saveas.new_name;
+ set_snapshot_name (saveas.new_name);
_name = saveas.new_name;
if (saveas.include_media && !saveas.copy_media) {
*/
}
}
-
+
bool was_dirty = dirty ();
- save_state ("", false, false, !saveas.include_media);
save_default_options ();
-
+
if (saveas.copy_media && saveas.copy_external) {
if (bring_all_sources_into_session (boost::bind (&Session::save_as_bring_callback, this, _1, _2, _3))) {
throw Glib::FileError (Glib::FileError::NO_SPACE_LEFT, "consolidate failed");
saveas.final_session_folder_name = _path;
store_recent_sessions (_name, _path);
-
+
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;
_name = old_name;
- _current_snapshot_name = old_snapshot;
+ set_snapshot_name (old_snapshot);
(*_session_dir) = old_sd;
config.set_audio_search_path (old_config_search_path[DataType::AUDIO]);
config.set_midi_search_path (old_config_search_path[DataType::MIDI]);
}
-
+
} else {
/* prune session dirs, and update disk space statistics
session_dirs.push_back (sp);
refresh_disk_space ();
- /* ensure that all existing tracks reset their current capture source paths
+ /* ensure that all existing tracks reset their current capture source paths
*/
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.
*/
} catch (Glib::FileError& e) {
saveas.failure_message = e.what();
-
+
/* recursively remove all the directories */
-
+
remove_directory (to_dir);
-
+
/* return error */
-
+
return -1;
} catch (...) {
saveas.failure_message = _("unknown reason");
-
+
/* recursively remove all the directories */
-
+
remove_directory (to_dir);
-
+
/* return error */
-
+
return -1;
}
-
+
return 0;
}
+
+static void set_progress (Progress* p, size_t n, size_t t)
+{
+ p->set_progress (float (n) / float(t));
+}
+
+int
+Session::archive_session (const std::string& dest,
+ const std::string& name,
+ ArchiveEncode compress_audio,
+ bool only_used_sources,
+ Progress* progress)
+{
+ if (dest.empty () || name.empty ()) {
+ return -1;
+ }
+
+ /* save current values */
+ bool was_dirty = dirty ();
+ string old_path = _path;
+ string old_name = _name;
+ string old_snapshot = _current_snapshot_name;
+ string old_sd = _session_dir->root_path();
+ string old_config_search_path[DataType::num_types];
+ old_config_search_path[DataType::AUDIO] = config.get_audio_search_path ();
+ old_config_search_path[DataType::MIDI] = config.get_midi_search_path ();
+
+ /* ensure that session-path is included in search-path */
+ bool ok = false;
+ for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+ if ((*sd).path == old_path) {
+ ok = true;
+ }
+ }
+ if (!ok) {
+ return -1;
+ }
+
+ /* create temporary dir to save session to */
+#ifdef PLATFORM_WINDOWS
+ char tmp[256] = "C:\\TEMP\\";
+ GetTempPath (sizeof (tmp), tmp);
+#else
+ char const* tmp = getenv("TMPDIR");
+ if (!tmp) {
+ tmp = "/tmp/";
+ }
+#endif
+ if ((strlen (tmp) + 21) > 1024) {
+ return -1;
+ }
+
+ char tmptpl[1024];
+ strcpy (tmptpl, tmp);
+ strcat (tmptpl, "ardourarchive-XXXXXX");
+ char* tmpdir = g_mkdtemp (tmptpl);
+
+ if (!tmpdir) {
+ return -1;
+ }
+
+ std::string to_dir = std::string (tmpdir);
+
+ /* switch session directory temporarily */
+ (*_session_dir) = to_dir;
+
+ if (!_session_dir->create()) {
+ (*_session_dir) = old_sd;
+ remove_directory (to_dir);
+ return -1;
+ }
+
+ /* prepare archive */
+ string archive = Glib::build_filename (dest, name + ".tar.xz");
+
+ PBD::ScopedConnectionList progress_connection;
+ PBD::FileArchive ar (archive);
+ if (progress) {
+ ar.progress.connect_same_thread (progress_connection, boost::bind (&set_progress, progress, _1, _2));
+ }
+
+ /* collect files to archive */
+ std::map<string,string> filemap;
+
+ vector<string> do_not_copy_extensions;
+ do_not_copy_extensions.push_back (statefile_suffix);
+ do_not_copy_extensions.push_back (pending_suffix);
+ do_not_copy_extensions.push_back (backup_suffix);
+ do_not_copy_extensions.push_back (temp_suffix);
+ do_not_copy_extensions.push_back (history_suffix);
+
+ vector<string> blacklist_dirs;
+ blacklist_dirs.push_back (string (peak_dir_name) + G_DIR_SEPARATOR);
+ blacklist_dirs.push_back (string (analysis_dir_name) + G_DIR_SEPARATOR);
+ blacklist_dirs.push_back (string (dead_dir_name) + G_DIR_SEPARATOR);
+ blacklist_dirs.push_back (string (export_dir_name) + G_DIR_SEPARATOR);
+ blacklist_dirs.push_back (string (externals_dir_name) + G_DIR_SEPARATOR);
+ 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) {
+ playlists->sync_all_regions_with_regions ();
+ playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot), false);
+ }
+
+ // collect audio sources for this session, calc total size for encoding
+ // add option to only include *used* sources (see Session::cleanup_sources)
+ size_t total_size = 0;
+ {
+ Glib::Threads::Mutex::Lock lm (source_lock);
+ for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+ boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (i->second);
+ if (!afs || afs->readable_length () == 0) {
+ continue;
+ }
+
+ if (only_used_sources) {
+ if (!afs->used()) {
+ continue;
+ }
+ if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
+ continue;
+ }
+ }
+
+ std::string from = afs->path();
+
+ if (compress_audio != NO_ENCODE) {
+ total_size += afs->readable_length ();
+ } else {
+ if (afs->within_session()) {
+ filemap[from] = make_new_media_path (from, name, name);
+ } else {
+ filemap[from] = make_new_media_path (from, name, name);
+ remove_dir_from_search_path (Glib::path_get_dirname (from), DataType::AUDIO);
+ }
+ }
+ }
+ }
+
+ /* encode audio */
+ if (compress_audio != NO_ENCODE) {
+ if (progress) {
+ progress->set_progress (2); // set to "encoding"
+ progress->set_progress (0);
+ }
+
+ Glib::Threads::Mutex::Lock lm (source_lock);
+ for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
+ boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (i->second);
+ if (!afs || afs->readable_length () == 0) {
+ continue;
+ }
+
+ if (only_used_sources) {
+ if (!afs->used()) {
+ continue;
+ }
+ if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
+ continue;
+ }
+ }
+
+ 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");
+ g_mkdir_with_parents (Glib::path_get_dirname (new_path).c_str (), 0755);
+
+ if (progress) {
+ progress->descend ((float)afs->readable_length () / total_size);
+ }
+
+ 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";
+ }
+
+ if (progress) {
+ progress->ascend ();
+ }
+ }
+ }
+
+ if (progress) {
+ progress->set_progress (-1); // set to "archiving"
+ progress->set_progress (0);
+ }
+
+ /* index files relevant for this session */
+ for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
+ vector<string> files;
+
+ size_t prefix_len = (*sd).path.size();
+ if (prefix_len > 0 && (*sd).path.at (prefix_len - 1) != G_DIR_SEPARATOR) {
+ ++prefix_len;
+ }
+
+ find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
+
+ static const std::string audiofile_dir_string = string (sound_dir_name) + G_DIR_SEPARATOR;
+ static const std::string videofile_dir_string = string (video_dir_name) + G_DIR_SEPARATOR;
+ static const std::string midifile_dir_string = string (midi_dir_name) + G_DIR_SEPARATOR;
+
+ for (vector<string>::const_iterator i = files.begin (); i != files.end (); ++i) {
+ std::string from = *i;
+
+#ifdef __APPLE__
+ string filename = Glib::path_get_basename (from);
+ std::transform (filename.begin(), filename.end(), filename.begin(), ::toupper);
+ if (filename == ".DS_STORE") {
+ continue;
+ }
+#endif
+
+ if (from.find (audiofile_dir_string) != string::npos) {
+ ; // handled above
+ } else if (from.find (midifile_dir_string) != string::npos) {
+ filemap[from] = make_new_media_path (from, name, name);
+ } else if (from.find (videofile_dir_string) != string::npos) {
+ filemap[from] = make_new_media_path (from, name, name);
+ } else {
+ bool do_copy = true;
+ for (vector<string>::iterator v = blacklist_dirs.begin(); v != blacklist_dirs.end(); ++v) {
+ if (from.find (*v) != string::npos) {
+ do_copy = false;
+ break;
+ }
+ }
+ for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
+ if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
+ do_copy = false;
+ break;
+ }
+ }
+
+ if (do_copy) {
+ filemap[from] = name + G_DIR_SEPARATOR + from.substr (prefix_len);
+ }
+ }
+ }
+ }
+
+ /* 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 ();
+
+ size_t prefix_len = _path.size();
+ if (prefix_len > 0 && _path.at (prefix_len - 1) != G_DIR_SEPARATOR) {
+ ++prefix_len;
+ }
+
+ /* collect session-state files */
+ vector<string> files;
+ do_not_copy_extensions.clear ();
+ do_not_copy_extensions.push_back (history_suffix);
+
+ blacklist_dirs.clear ();
+ blacklist_dirs.push_back (string (externals_dir_name) + G_DIR_SEPARATOR);
+
+ find_files_matching_filter (files, to_dir, accept_all_files, 0, false, true, true);
+ for (vector<string>::const_iterator i = files.begin (); i != files.end (); ++i) {
+ std::string from = *i;
+ bool do_copy = true;
+ for (vector<string>::iterator v = blacklist_dirs.begin(); v != blacklist_dirs.end(); ++v) {
+ if (from.find (*v) != string::npos) {
+ do_copy = false;
+ break;
+ }
+ }
+ for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
+ if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
+ do_copy = false;
+ break;
+ }
+ }
+ if (do_copy) {
+ filemap[from] = name + G_DIR_SEPARATOR + from.substr (prefix_len);
+ }
+ }
+
+ /* restore original values */
+ _path = old_path;
+ _name = old_name;
+ set_snapshot_name (old_snapshot);
+ (*_session_dir) = old_sd;
+ if (was_dirty) {
+ set_dirty ();
+ }
+ config.set_audio_search_path (old_config_search_path[DataType::AUDIO]);
+ config.set_midi_search_path (old_config_search_path[DataType::MIDI]);
+
+ 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);
+
+ return rv;
+}
+
+void
+Session::undo (uint32_t n)
+{
+ if (actively_recording()) {
+ return;
+ }
+
+ _history.undo (n);
+}
+
+void
+Session::redo (uint32_t n)
+{
+ if (actively_recording()) {
+ return;
+ }
+
+ _history.redo (n);
+}