2008-06-02 17:41:35 -04:00
|
|
|
/*
|
2019-08-02 22:01:25 -04:00
|
|
|
* Copyright (C) 1999-2019 Paul Davis <paul@linuxaudiosystems.com>
|
|
|
|
* Copyright (C) 2005-2006 Taybin Rutkin <taybin@taybin.com>
|
|
|
|
* Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
|
|
|
|
* Copyright (C) 2006-2016 Tim Mayberry <mojofunk@gmail.com>
|
|
|
|
* Copyright (C) 2006 Jesse Chappell <jesse@essej.net>
|
|
|
|
* Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
|
|
|
|
* Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com>
|
|
|
|
* Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
|
|
|
|
* Copyright (C) 2013-2016 John Emmas <john@creativepost.co.uk>
|
|
|
|
* Copyright (C) 2013-2017 Nick Mainsbridge <mainsbridge@gmail.com>
|
|
|
|
* Copyright (C) 2014-2018 Ben Loftis <ben@harrisonconsoles.com>
|
|
|
|
* Copyright (C) 2017 Johannes Mueller <github@johannes-mueller.org>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-07-12 20:26:28 -04:00
|
|
|
|
|
|
|
#ifdef WAF_BUILD
|
|
|
|
#include "libardour-config.h"
|
|
|
|
#endif
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <string>
|
|
|
|
#include <cerrno>
|
|
|
|
#include <cstdio> /* snprintf(3) ... grrr */
|
|
|
|
#include <cmath>
|
2016-05-17 15:06:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <climits>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <sys/time.h>
|
2020-03-24 12:38:03 -04:00
|
|
|
/* for open(2) */
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
#ifdef HAVE_SYS_VFS_H
|
|
|
|
#include <sys/vfs.h>
|
|
|
|
#endif
|
|
|
|
|
2016-06-26 10:43:07 -04:00
|
|
|
#if defined(__APPLE__) || defined(__FreeBSD__)
|
2013-08-09 09:17:03 -04:00
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/mount.h>
|
|
|
|
#endif
|
|
|
|
|
2012-06-12 12:41:29 -04:00
|
|
|
#ifdef HAVE_SYS_STATVFS_H
|
|
|
|
#include <sys/statvfs.h>
|
|
|
|
#endif
|
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
#include <glib.h>
|
2015-10-05 16:10:58 -04:00
|
|
|
#include "pbd/gstdio_compat.h"
|
2016-09-04 20:22:39 -04:00
|
|
|
#include "pbd/locale_guard.h"
|
2011-07-14 13:41:06 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
#include <glibmm.h>
|
2012-07-25 13:48:55 -04:00
|
|
|
#include <glibmm/threads.h>
|
2015-01-11 12:15:14 -05:00
|
|
|
#include <glibmm/fileutils.h>
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2011-10-24 15:47:06 -04:00
|
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "midi++/mmc.h"
|
|
|
|
#include "midi++/port.h"
|
|
|
|
|
2019-10-25 15:13:51 -04:00
|
|
|
#include "evoral/SMF.h"
|
2012-05-24 02:09:29 -04:00
|
|
|
|
2011-03-01 16:54:54 -05:00
|
|
|
#include "pbd/basename.h"
|
2015-08-18 23:45:01 -04:00
|
|
|
#include "pbd/debug.h"
|
2009-12-03 13:44:06 -05:00
|
|
|
#include "pbd/enumwriter.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "pbd/error.h"
|
2012-06-23 01:07:49 -04:00
|
|
|
#include "pbd/file_utils.h"
|
2013-07-11 14:57:16 -04:00
|
|
|
#include "pbd/pathexpand.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "pbd/pthread_utils.h"
|
2020-03-24 12:38:03 -04:00
|
|
|
#include "pbd/scoped_file_descriptor.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "pbd/stacktrace.h"
|
2016-08-28 07:12:16 -04:00
|
|
|
#include "pbd/types_convert.h"
|
2013-07-11 15:00:56 -04:00
|
|
|
#include "pbd/localtime_r.h"
|
2015-12-19 08:46:15 -05:00
|
|
|
#include "pbd/unwind.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
|
2009-12-30 14:33:52 -05:00
|
|
|
#include "ardour/amp.h"
|
2014-04-28 19:58:24 -04:00
|
|
|
#include "ardour/async_midi_port.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/audio_track.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/audioengine.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/audiofilesource.h"
|
|
|
|
#include "ardour/audioregion.h"
|
2016-05-17 15:06:01 -04:00
|
|
|
#include "ardour/auditioner.h"
|
2010-11-28 16:28:54 -05:00
|
|
|
#include "ardour/automation_control.h"
|
2016-05-07 13:32:31 -04:00
|
|
|
#include "ardour/boost_debug.h"
|
2009-10-23 20:39:28 -04:00
|
|
|
#include "ardour/butler.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/control_protocol_manager.h"
|
|
|
|
#include "ardour/directory_names.h"
|
2017-04-10 05:25:00 -04:00
|
|
|
#include "ardour/disk_reader.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/filename_extensions.h"
|
2013-09-09 22:40:54 -04:00
|
|
|
#include "ardour/graph.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/location.h"
|
2016-12-08 15:37:07 -05:00
|
|
|
#ifdef LV2_SUPPORT
|
2016-09-20 22:05:45 -04:00
|
|
|
#include "ardour/lv2_plugin.h"
|
2016-12-08 15:37:07 -05:00
|
|
|
#endif
|
2011-10-19 17:53:09 -04:00
|
|
|
#include "ardour/midi_model.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/midi_region.h"
|
2014-04-28 19:58:24 -04:00
|
|
|
#include "ardour/midi_scene_changer.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/midi_source.h"
|
|
|
|
#include "ardour/midi_track.h"
|
2012-05-24 02:09:29 -04:00
|
|
|
#include "ardour/playlist_factory.h"
|
2014-10-10 08:12:48 -04:00
|
|
|
#include "ardour/playlist_source.h"
|
2010-06-02 12:21:02 -04:00
|
|
|
#include "ardour/port.h"
|
2012-05-24 02:09:29 -04:00
|
|
|
#include "ardour/processor.h"
|
2016-09-20 13:46:07 -04:00
|
|
|
#include "ardour/progress.h"
|
2015-05-08 12:07:33 -04:00
|
|
|
#include "ardour/profile.h"
|
2011-02-11 11:14:54 -05:00
|
|
|
#include "ardour/proxy_controllable.h"
|
2011-07-18 13:08:50 -04:00
|
|
|
#include "ardour/recent_sessions.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/region_factory.h"
|
2016-06-01 16:27:55 -04:00
|
|
|
#include "ardour/revision.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/route_group.h"
|
|
|
|
#include "ardour/send.h"
|
2017-05-05 07:31:21 -04:00
|
|
|
#include "ardour/selection.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/session.h"
|
|
|
|
#include "ardour/session_directory.h"
|
|
|
|
#include "ardour/session_metadata.h"
|
2009-12-03 16:52:10 -05:00
|
|
|
#include "ardour/session_playlists.h"
|
2012-05-24 02:09:29 -04:00
|
|
|
#include "ardour/session_state_utils.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/silentfilesource.h"
|
2017-06-24 19:56:53 -04:00
|
|
|
#include "ardour/smf_source.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/sndfilesource.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/source_factory.h"
|
2011-10-18 23:34:02 -04:00
|
|
|
#include "ardour/speakers.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/template_utils.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/tempo.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/ticker.h"
|
2018-09-18 18:51:59 -04:00
|
|
|
#include "ardour/transport_master_manager.h"
|
2016-08-28 07:12:16 -04:00
|
|
|
#include "ardour/types_convert.h"
|
2009-10-13 16:43:28 -04:00
|
|
|
#include "ardour/user_bundle.h"
|
2016-03-07 16:33:15 -05:00
|
|
|
#include "ardour/vca.h"
|
2016-01-26 00:18:03 -05:00
|
|
|
#include "ardour/vca_manager.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-02-25 13:39:39 -05:00
|
|
|
#include "control_protocol/control_protocol.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-02-23 09:41:21 -05:00
|
|
|
#include "LuaBridge/LuaBridge.h"
|
|
|
|
|
2016-07-14 14:44:52 -04:00
|
|
|
#include "pbd/i18n.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
#include <locale.h>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace ARDOUR;
|
|
|
|
using namespace PBD;
|
|
|
|
|
2015-08-18 23:45:01 -04:00
|
|
|
#define DEBUG_UNDO_HISTORY(msg) DEBUG_TRACE (PBD::DEBUG::UndoHistory, string_compose ("%1: %2\n", __LINE__, msg));
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
void
|
2013-09-10 15:41:19 -04:00
|
|
|
Session::pre_engine_init (string fullpath)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2013-09-10 15:41:19 -04:00
|
|
|
if (fullpath.empty()) {
|
2008-06-02 17:41:35 -04:00
|
|
|
destroy ();
|
|
|
|
throw failed_constructor();
|
|
|
|
}
|
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
/* discover canonical fullpath */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-27 08:53:30 -04:00
|
|
|
_path = canonical_path(fullpath);
|
2013-09-10 15:41:19 -04:00
|
|
|
|
|
|
|
/* is it new ? */
|
2015-06-17 08:32:10 -04:00
|
|
|
|
2019-09-25 15:02:31 -04:00
|
|
|
_is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-09 22:40:54 -04:00
|
|
|
/* finish initialization that can't be done in a normal C++ constructor
|
|
|
|
definition.
|
2008-06-02 17:41:35 -04:00
|
|
|
*/
|
|
|
|
|
2013-09-09 22:40:54 -04:00
|
|
|
timerclear (&last_mmc_step);
|
2008-06-02 17:41:35 -04:00
|
|
|
g_atomic_int_set (&processing_prohibited, 0);
|
|
|
|
g_atomic_int_set (&_record_status, Disabled);
|
|
|
|
g_atomic_int_set (&_playback_load, 100);
|
|
|
|
g_atomic_int_set (&_capture_load, 100);
|
2013-09-09 22:40:54 -04:00
|
|
|
set_next_event ();
|
|
|
|
_all_route_group->set_active (true, this);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-05-15 21:53:43 -04:00
|
|
|
if (config.get_use_video_sync()) {
|
2008-06-02 17:41:35 -04:00
|
|
|
waiting_for_sync_offset = true;
|
|
|
|
} else {
|
|
|
|
waiting_for_sync_offset = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
last_rr_session_dir = session_dirs.begin();
|
2013-09-09 22:40:54 -04:00
|
|
|
|
|
|
|
set_history_depth (Config->get_history_depth());
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
/* default: assume simple stereo speaker configuration */
|
2011-02-09 11:42:18 -05:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
_speakers->setup_default_speakers (2);
|
2011-02-09 11:42:18 -05:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
_solo_cut_control.reset (new ProxyControllable (_("solo cut control (dB)"), PBD::Controllable::GainLike,
|
|
|
|
boost::bind (&RCConfiguration::set_solo_mute_gain, Config, _1),
|
|
|
|
boost::bind (&RCConfiguration::get_solo_mute_gain, Config)));
|
|
|
|
add_controllable (_solo_cut_control);
|
2011-02-11 11:14:54 -05:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
/* These are all static "per-class" signals */
|
|
|
|
|
2009-12-21 13:23:07 -05:00
|
|
|
SourceFactory::SourceCreated.connect_same_thread (*this, boost::bind (&Session::add_source, this, _1));
|
|
|
|
PlaylistFactory::PlaylistCreated.connect_same_thread (*this, boost::bind (&Session::add_playlist, this, _1, _2));
|
|
|
|
AutomationList::AutomationListCreated.connect_same_thread (*this, boost::bind (&Session::add_automation_list, this, _1));
|
|
|
|
IO::PortCountChanged.connect_same_thread (*this, boost::bind (&Session::ensure_buffers, this, _1));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
/* stop IO objects from doing stuff until we're ready for them */
|
|
|
|
|
2009-06-09 16:21:19 -04:00
|
|
|
Delivery::disable_panners ();
|
2008-06-02 17:41:35 -04:00
|
|
|
IO::disable_connecting ();
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2013-09-10 15:41:19 -04:00
|
|
|
Session::post_engine_init ()
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2013-09-10 15:41:19 -04:00
|
|
|
BootMessage (_("Set block size and sample rate"));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
set_block_size (_engine.samples_per_cycle());
|
2017-09-18 12:39:17 -04:00
|
|
|
set_sample_rate (_engine.sample_rate());
|
2013-09-09 22:40:54 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
BootMessage (_("Using configuration"));
|
2013-09-09 22:40:54 -04:00
|
|
|
|
|
|
|
_midi_ports = new MidiPortManager;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-04-28 19:58:24 -04:00
|
|
|
MIDISceneChanger* msc;
|
|
|
|
|
|
|
|
_scene_changer = msc = new MIDISceneChanger (*this);
|
2015-12-07 12:02:42 -05:00
|
|
|
msc->set_input_port (boost::dynamic_pointer_cast<MidiPort>(scene_input_port()));
|
|
|
|
msc->set_output_port (boost::dynamic_pointer_cast<MidiPort>(scene_output_port()));
|
2014-04-28 19:58:24 -04:00
|
|
|
|
2017-09-18 12:39:17 -04:00
|
|
|
boost::function<samplecnt_t(void)> timer_func (boost::bind (&Session::audible_sample, this, (bool*)(0)));
|
2015-12-07 12:02:42 -05:00
|
|
|
boost::dynamic_pointer_cast<AsyncMIDIPort>(scene_input_port())->set_timer (timer_func);
|
2014-04-28 19:58:24 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
setup_midi_machine_control ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2009-10-23 20:39:28 -04:00
|
|
|
if (_butler->start_thread()) {
|
2016-09-28 12:27:47 -04:00
|
|
|
error << _("Butler did not start") << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2011-12-26 19:57:20 -05:00
|
|
|
if (start_midi_thread ()) {
|
2016-09-28 12:27:47 -04:00
|
|
|
error << _("MIDI I/O thread did not start") << endmsg;
|
2011-12-26 19:57:20 -05:00
|
|
|
return -1;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
setup_click_sounds (0);
|
|
|
|
setup_midi_control ();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
_engine.Halted.connect_same_thread (*this, boost::bind (&Session::engine_halted, this));
|
|
|
|
_engine.Xrun.connect_same_thread (*this, boost::bind (&Session::xrun_recovery, this));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
try {
|
|
|
|
/* tempo map requires sample rate knowledge */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2015-04-01 10:58:56 -04:00
|
|
|
delete _tempo_map;
|
2017-09-18 12:39:17 -04:00
|
|
|
_tempo_map = new TempoMap (_current_sample_rate);
|
2013-09-10 15:41:19 -04:00
|
|
|
_tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
|
2017-01-26 11:05:32 -05:00
|
|
|
_tempo_map->MetricPositionChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1));
|
2017-06-27 14:28:45 -04:00
|
|
|
} catch (std::exception const & e) {
|
|
|
|
error << _("Unexpected exception during session setup: ") << e.what() << endmsg;
|
|
|
|
return -2;
|
|
|
|
} catch (...) {
|
|
|
|
error << _("Unknown exception during session setup") << endmsg;
|
|
|
|
return -3;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2017-06-27 14:28:45 -04:00
|
|
|
try {
|
2013-09-10 15:41:19 -04:00
|
|
|
/* MidiClock requires a tempo map */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-04-24 08:41:07 -04:00
|
|
|
delete midi_clock;
|
2013-09-10 15:41:19 -04:00
|
|
|
midi_clock = new MidiClockTicker ();
|
|
|
|
midi_clock->set_session (this);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
/* crossfades require sample rate knowledge */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2019-10-28 19:34:23 -04:00
|
|
|
_engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this, true));
|
2016-10-19 17:17:30 -04:00
|
|
|
_engine.MidiSelectionPortsChanged.connect_same_thread (*this, boost::bind (&Session::rewire_midi_selection_ports, this));
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2017-04-10 05:25:00 -04:00
|
|
|
DiskReader::allocate_working_buffers();
|
2013-09-10 15:41:19 -04:00
|
|
|
refresh_disk_space ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
/* we're finally ready to call set_state() ... all objects have
|
|
|
|
* been created, the engine is running.
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
if (state_tree) {
|
2017-08-17 13:28:14 -04:00
|
|
|
try {
|
|
|
|
if (set_state (*state_tree->root(), Stateful::loading_state_version)) {
|
|
|
|
error << _("Could not set session state from XML") << endmsg;
|
|
|
|
return -4;
|
|
|
|
}
|
|
|
|
} catch (PBD::unknown_enumeration& e) {
|
|
|
|
error << _("Session state: ") << e.what() << endmsg;
|
2017-06-27 14:28:45 -04:00
|
|
|
return -4;
|
2013-09-10 15:41:19 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// set_state() will call setup_raid_path(), but if it's a new session we need
|
|
|
|
// to call setup_raid_path() here.
|
|
|
|
setup_raid_path (_path);
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
/* ENGINE */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
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));
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
Config->map_parameters (ff);
|
|
|
|
config.map_parameters (ft);
|
2017-04-20 17:47:39 -04:00
|
|
|
_butler->map_parameters ();
|
2012-01-27 08:22:55 -05:00
|
|
|
|
2019-03-17 12:19:02 -04:00
|
|
|
/* Configure all processors; now that the
|
|
|
|
* engine is running, ports are re-established,
|
|
|
|
* and IOChange are complete.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ());
|
|
|
|
ProcessorChangeBlocker pcb (this);
|
|
|
|
boost::shared_ptr<RouteList> r = routes.reader ();
|
|
|
|
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
|
|
|
|
(*i)->configure_processors (0);
|
|
|
|
}
|
2020-02-14 12:42:45 -05:00
|
|
|
/* release process-lock, ProcessorChangeBlocker may trigger
|
|
|
|
* latency-callback from non-rt thread which may take the lock */
|
|
|
|
lx.release ();
|
2019-03-17 12:19:02 -04:00
|
|
|
}
|
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
/* Reset all panners */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
Delivery::reset_panners ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
/* 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.
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
ControlProtocolManager::instance().set_session (this);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
/* This must be done after the ControlProtocolManager set_session above,
|
|
|
|
as it will set states for ports which the ControlProtocolManager creates.
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
// XXX set state of MIDI::Port's
|
|
|
|
// MidiPortManager::instance()->set_port_states (Config->midi_port_states ());
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
/* 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.
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
hookup_io ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
/* Let control protocols know that we are now all connected, so they
|
|
|
|
* could start talking to surfaces if they want to.
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
ControlProtocolManager::instance().midi_connectivity_established ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-09-16 12:08:19 -04:00
|
|
|
if (_is_new && !no_auto_connect()) {
|
|
|
|
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock());
|
|
|
|
auto_connect_master_bus ();
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2019-03-18 10:33:05 -04:00
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave | Dirty));
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2019-12-16 18:13:27 -05:00
|
|
|
/* update latencies */
|
|
|
|
|
|
|
|
initialize_latencies ();
|
|
|
|
|
2014-10-24 12:18:40 -04:00
|
|
|
_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));
|
2013-09-10 15:41:19 -04:00
|
|
|
_locations->changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this));
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2019-11-15 17:54:41 -05:00
|
|
|
if (synced_to_engine()) {
|
|
|
|
_engine.transport_stop ();
|
|
|
|
}
|
|
|
|
|
|
|
|
// send_full_time_code (0);
|
|
|
|
|
2013-09-10 15:41:19 -04:00
|
|
|
} catch (AudioEngine::PortRegistrationFailure& err) {
|
The great audio processing overhaul.
The vast majority of Route signal processing is now simply in the list of
processors. There are definitely regressions here, but there's also
a lot of things fixed. It's far too much work to let diverge anymore
regardless, so here it is.
The basic model is: A route has a fixed set of input channels (matching
its JACK input ports and diskstream). The first processor takes this
as input. The next processor is configured using the first processor's
output as input, and is allowed to choose whatever output it wants
given that input... and so on, and so on. Finally, the last processor's
requested output is used to set up the panner and create whatever Jack
ports are needed to output the data.
All 'special' internal processors (meter, fader, amp, insert, send) are
currently transparent: they read any input, and return the same set
of channels back (unmodified, except for amp).
User visible changes:
* LV2 Instrument support (tracks with both MIDI and audio channels)
* MIDI in/out plugin support
* Generic plugin replication (for MIDI plugins, MIDI/audio plugins)
* Movable meter point
Known Bugs:
* Things seem to get weird on loaded sessions
* Output delivery is sketchy
* 2.0 session loading was probably already broken...
but it's definitely broken now :)
Please test this and file bugs if you have any time...
git-svn-id: svn://localhost/ardour2/branches/3.0@5055 d708f5d6-7413-0410-9779-e7cbd77b26cf
2009-05-07 02:30:50 -04:00
|
|
|
error << err.what() << endmsg;
|
2017-06-27 14:28:45 -04:00
|
|
|
return -5;
|
2016-09-28 12:27:47 -04:00
|
|
|
} catch (std::exception const & e) {
|
|
|
|
error << _("Unexpected exception during session setup: ") << e.what() << endmsg;
|
2017-06-27 14:28:45 -04:00
|
|
|
return -6;
|
2013-09-10 15:41:19 -04:00
|
|
|
} catch (...) {
|
2016-09-28 12:27:47 -04:00
|
|
|
error << _("Unknown exception during session setup") << endmsg;
|
2017-06-27 14:28:45 -04:00
|
|
|
return -7;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
BootMessage (_("Reset Remote Controls"));
|
|
|
|
|
2014-10-22 17:06:53 -04:00
|
|
|
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
|
|
|
|
send_immediate_mmc (MIDI::MachineControlCommand (Timecode::Time ()));
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2012-10-21 10:07:10 -04:00
|
|
|
ltc_tx_initialize();
|
2019-09-16 16:56:54 -04:00
|
|
|
|
2013-09-12 16:27:58 -04:00
|
|
|
Port::set_connecting_blocked (false);
|
|
|
|
|
2020-02-24 16:16:56 -05:00
|
|
|
set_clean ();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2015-01-29 22:21:58 -05:00
|
|
|
/* Now, finally, we can fill the playback buffers */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-29 22:21:58 -05:00
|
|
|
BootMessage (_("Filling playback buffers"));
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-29 22:21:58 -05:00
|
|
|
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);
|
2017-07-25 11:26:14 -04:00
|
|
|
if (trk && !trk->is_private_route()) {
|
2017-09-18 12:39:17 -04:00
|
|
|
trk->seek (_transport_sample, true);
|
2015-01-29 22:21:58 -05:00
|
|
|
}
|
|
|
|
}
|
2015-05-12 16:38:13 -04:00
|
|
|
|
2020-02-24 16:21:18 -05:00
|
|
|
reset_xrun_count ();
|
2015-05-12 16:38:13 -04:00
|
|
|
return 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2015-05-08 20:56:10 -04:00
|
|
|
void
|
|
|
|
Session::session_loaded ()
|
|
|
|
{
|
|
|
|
SessionLoaded();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2020-02-24 16:16:56 -05:00
|
|
|
set_clean ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-08 20:56:10 -04:00
|
|
|
if (_is_new) {
|
|
|
|
save_state ("");
|
|
|
|
} else if (state_was_pending) {
|
|
|
|
save_state ("");
|
|
|
|
state_was_pending = false;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-08 20:56:10 -04:00
|
|
|
/* Now, finally, we can fill the playback buffers */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-08 20:56:10 -04:00
|
|
|
BootMessage (_("Filling playback buffers"));
|
2020-01-20 13:07:27 -05:00
|
|
|
force_locate (_transport_sample, MustStop);
|
2020-02-24 16:21:18 -05:00
|
|
|
reset_xrun_count ();
|
2015-05-08 20:56:10 -04:00
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
string
|
|
|
|
Session::raid_path () const
|
|
|
|
{
|
2013-08-15 06:04:08 -04:00
|
|
|
Searchpath raid_search_path;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
2012-06-23 01:06:54 -04:00
|
|
|
raid_search_path += (*i).path;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return raid_search_path.to_string ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Session::setup_raid_path (string path)
|
|
|
|
{
|
|
|
|
if (path.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
space_and_path sp;
|
|
|
|
string fspath;
|
|
|
|
|
|
|
|
session_dirs.clear ();
|
|
|
|
|
2013-08-15 06:04:08 -04:00
|
|
|
Searchpath search_path(path);
|
|
|
|
Searchpath sound_search_path;
|
|
|
|
Searchpath midi_search_path;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-08-15 06:04:08 -04:00
|
|
|
for (Searchpath::const_iterator i = search_path.begin(); i != search_path.end(); ++i) {
|
2012-06-23 01:06:54 -04:00
|
|
|
sp.path = *i;
|
2008-06-02 17:41:35 -04:00
|
|
|
sp.blocks = 0; // not needed
|
|
|
|
session_dirs.push_back (sp);
|
|
|
|
|
|
|
|
SessionDirectory sdir(sp.path);
|
|
|
|
|
|
|
|
sound_search_path += sdir.sound_path ();
|
|
|
|
midi_search_path += sdir.midi_path ();
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset the round-robin soundfile path thingie
|
|
|
|
last_rr_session_dir = session_dirs.begin();
|
|
|
|
}
|
|
|
|
|
2009-11-30 08:16:38 -05:00
|
|
|
bool
|
|
|
|
Session::path_is_within_session (const std::string& path)
|
|
|
|
{
|
|
|
|
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
2012-06-23 01:08:14 -04:00
|
|
|
if (PBD::path_is_within (i->path, path)) {
|
2009-11-30 08:16:38 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
int
|
|
|
|
Session::ensure_subdirs ()
|
|
|
|
{
|
|
|
|
string dir;
|
|
|
|
|
2012-06-23 01:06:54 -04:00
|
|
|
dir = session_directory().peak_path();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-06-23 01:06:54 -04:00
|
|
|
dir = session_directory().sound_path();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2012-06-23 01:06:54 -04:00
|
|
|
dir = session_directory().midi_path();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-06-23 01:06:54 -04:00
|
|
|
dir = session_directory().dead_path();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-06-23 01:06:54 -04:00
|
|
|
dir = session_directory().export_path();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-04-12 12:32:34 -04:00
|
|
|
if(Profile->get_mixbus()) {
|
|
|
|
dir = session_directory().backup_path();
|
|
|
|
|
|
|
|
if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
|
|
|
|
error << string_compose(_("Session: cannot create session backup folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-11-25 17:12:03 -05:00
|
|
|
dir = plugins_dir ();
|
|
|
|
|
|
|
|
if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
|
|
|
|
error << string_compose(_("Session: cannot create session plugins folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-01-27 20:45:15 -05:00
|
|
|
dir = externals_dir ();
|
|
|
|
|
|
|
|
if (g_mkdir_with_parents (dir.c_str(), 0755) < 0) {
|
|
|
|
error << string_compose(_("Session: cannot create session externals folder \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-12-11 15:38:42 -05:00
|
|
|
/** @param session_template directory containing session template, or empty.
|
|
|
|
* Caller must not hold process lock.
|
|
|
|
*/
|
2008-06-02 17:41:35 -04:00
|
|
|
int
|
2020-03-24 12:38:03 -04:00
|
|
|
Session::create (const string& session_template, BusProfile const * bus_profile, bool unnamed)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-03-24 12:38:03 -04:00
|
|
|
if (unnamed) {
|
2020-03-24 15:20:24 -04:00
|
|
|
PBD::ScopedFileDescriptor fd = g_open (unnamed_file_name().c_str(), O_CREAT|O_TRUNC|O_RDWR, 0666);
|
2020-03-24 12:38:03 -04:00
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (ensure_subdirs ()) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-06-23 01:08:19 -04:00
|
|
|
_writable = exists_and_writable (_path);
|
2011-04-11 14:49:51 -04:00
|
|
|
|
2011-12-11 15:38:42 -05:00
|
|
|
if (!session_template.empty()) {
|
2019-09-25 15:02:31 -04:00
|
|
|
string in_path = session_template_dir_to_file (session_template);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2015-09-15 08:17:22 -04:00
|
|
|
FILE* in = g_fopen (in_path.c_str(), "rb");
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2019-07-18 15:38:43 -04:00
|
|
|
if (in) {
|
|
|
|
/* no need to call legalize_for_path() since the string
|
|
|
|
* in session_template is already a legal path name
|
|
|
|
*/
|
|
|
|
string out_path = Glib::build_filename (_session_dir->root_path(), _name + statefile_suffix);
|
|
|
|
|
|
|
|
FILE* out = g_fopen (out_path.c_str(), "wb");
|
|
|
|
|
|
|
|
if (out) {
|
|
|
|
char buf[1024];
|
|
|
|
stringstream new_session;
|
|
|
|
|
|
|
|
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);
|
|
|
|
fclose (out);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (charsRead == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
new_session.write (buf, charsRead);
|
|
|
|
}
|
|
|
|
fclose (in);
|
2015-09-15 08:17:22 -04:00
|
|
|
|
2019-07-18 15:38:43 -04:00
|
|
|
string file_contents = new_session.str();
|
|
|
|
size_t writeSize = file_contents.length();
|
|
|
|
if (fwrite (file_contents.c_str(), sizeof(char), writeSize, out) != writeSize) {
|
|
|
|
error << string_compose (_("Error writing session template file %1 (%2)"), out_path, strerror (errno)) << endmsg;
|
|
|
|
fclose (out);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
fclose (out);
|
2015-09-15 08:17:22 -04:00
|
|
|
|
2019-07-18 15:38:43 -04:00
|
|
|
_is_new = false;
|
2019-07-18 14:27:00 -04:00
|
|
|
|
2019-09-25 15:02:31 -04:00
|
|
|
/* Copy plugin state files from template to new session */
|
|
|
|
std::string template_plugins = Glib::build_filename (session_template, X_("plugins"));
|
|
|
|
copy_recurse (template_plugins, plugins_dir ());
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2019-07-18 15:38:43 -04:00
|
|
|
return 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2019-07-18 15:38:43 -04:00
|
|
|
} else {
|
|
|
|
error << string_compose (_("Could not open %1 for writing session template"), out_path)
|
|
|
|
<< endmsg;
|
|
|
|
fclose(in);
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-07-18 15:38:43 -04:00
|
|
|
} else {
|
|
|
|
error << string_compose (_("Could not open session template %1 for reading"), in_path)
|
|
|
|
<< endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
/* set up Master Out and Monitor Out if necessary */
|
2010-03-22 17:35:35 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (bus_profile) {
|
2010-03-22 17:35:35 -04:00
|
|
|
RouteList rl;
|
2017-04-20 17:47:39 -04:00
|
|
|
ChanCount count(DataType::AUDIO, bus_profile->master_out_channels);
|
2017-08-15 17:17:08 -04:00
|
|
|
if (bus_profile->master_out_channels) {
|
|
|
|
int rv = add_master_bus (count);
|
2010-03-22 17:35:35 -04:00
|
|
|
|
2017-08-15 17:17:08 -04:00
|
|
|
if (rv) {
|
|
|
|
return rv;
|
2017-04-20 17:47:39 -04:00
|
|
|
}
|
2016-05-07 13:32:31 -04:00
|
|
|
|
2017-08-15 17:17:08 -04:00
|
|
|
if (Config->get_use_monitor_bus())
|
|
|
|
add_monitor_section ();
|
2010-03-22 17:35:35 -04:00
|
|
|
}
|
2012-01-17 20:30:44 -05:00
|
|
|
}
|
|
|
|
|
2020-02-24 16:16:56 -05:00
|
|
|
set_clean ();
|
2020-02-24 16:21:18 -05:00
|
|
|
reset_xrun_count ();
|
2020-02-24 16:16:56 -05:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Session::maybe_write_autosave()
|
|
|
|
{
|
2017-04-20 17:47:39 -04:00
|
|
|
if (dirty() && record_status() != Recording) {
|
|
|
|
save_state("", true);
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Session::remove_pending_capture_state ()
|
|
|
|
{
|
2012-06-23 01:08:26 -04:00
|
|
|
std::string pending_state_file_path(_session_dir->root_path());
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2012-06-23 01:08:26 -04:00
|
|
|
pending_state_file_path = Glib::build_filename (pending_state_file_path, legalize_for_path (_current_snapshot_name) + pending_suffix);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2019-08-15 23:31:50 -04:00
|
|
|
if (!Glib::file_test (pending_state_file_path, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
return;
|
|
|
|
}
|
2012-06-23 01:08:29 -04:00
|
|
|
|
2019-08-15 23:31:50 -04:00
|
|
|
if (::g_unlink (pending_state_file_path.c_str()) != 0) {
|
2012-06-23 01:08:26 -04:00
|
|
|
error << string_compose(_("Could not remove pending capture state at path \"%1\" (%2)"),
|
|
|
|
pending_state_file_path, g_strerror (errno)) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2019-08-15 23:31:50 -04:00
|
|
|
#ifndef NDEBUG
|
|
|
|
else {
|
|
|
|
cerr << "removed " << pending_state_file_path << endl;
|
|
|
|
}
|
|
|
|
#endif
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Rename a state file.
|
2011-05-23 18:12:45 -04:00
|
|
|
* @param old_name Old snapshot name.
|
|
|
|
* @param new_name New snapshot name.
|
2008-06-02 17:41:35 -04:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
Session::rename_state (string old_name, string new_name)
|
|
|
|
{
|
|
|
|
if (old_name == _current_snapshot_name || old_name == _name) {
|
|
|
|
/* refuse to rename the current snapshot or the "main" one */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-07-12 20:26:28 -04:00
|
|
|
const string old_xml_filename = legalize_for_path (old_name) + statefile_suffix;
|
|
|
|
const string new_xml_filename = legalize_for_path (new_name) + statefile_suffix;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:08:40 -04:00
|
|
|
const std::string old_xml_path(Glib::build_filename (_session_dir->root_path(), old_xml_filename));
|
|
|
|
const std::string new_xml_path(Glib::build_filename (_session_dir->root_path(), new_xml_filename));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:08:40 -04:00
|
|
|
if (::g_rename (old_xml_path.c_str(), new_xml_path.c_str()) != 0) {
|
2008-06-02 17:41:35 -04:00
|
|
|
error << string_compose(_("could not rename snapshot %1 to %2 (%3)"),
|
2012-06-23 01:08:40 -04:00
|
|
|
old_name, new_name, g_strerror(errno)) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Remove a state file.
|
2011-05-23 18:12:45 -04:00
|
|
|
* @param snapshot_name Snapshot name.
|
2008-06-02 17:41:35 -04:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
Session::remove_state (string snapshot_name)
|
|
|
|
{
|
2012-06-15 22:50:20 -04:00
|
|
|
if (!_writable || snapshot_name == _current_snapshot_name || snapshot_name == _name) {
|
2008-06-02 17:41:35 -04:00
|
|
|
// refuse to remove the current snapshot or the "main" one
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-06-23 01:07:02 -04:00
|
|
|
std::string xml_path(_session_dir->root_path());
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:07:02 -04:00
|
|
|
xml_path = Glib::build_filename (xml_path, legalize_for_path (snapshot_name) + statefile_suffix);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
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
|
2012-06-23 01:08:31 -04:00
|
|
|
if (g_remove (xml_path.c_str()) != 0) {
|
2013-01-09 09:33:10 -05:00
|
|
|
error << string_compose(_("Could not remove session file at path \"%1\" (%2)"),
|
2012-06-23 01:08:31 -04:00
|
|
|
xml_path, g_strerror (errno)) << endmsg;
|
|
|
|
}
|
2018-07-14 13:46:49 -04:00
|
|
|
|
|
|
|
StateSaved (snapshot_name); /* EMIT SIGNAL */
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2011-05-04 07:32:35 -04:00
|
|
|
/** @param snapshot_name Name to save under, without .ardour / .pending prefix */
|
2008-06-02 17:41:35 -04:00
|
|
|
int
|
2017-10-03 23:06:47 -04:00
|
|
|
Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot, bool template_only, bool for_archive, bool only_used_assets)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2016-05-07 06:19:41 -04:00
|
|
|
DEBUG_TRACE (DEBUG::Locale, string_compose ("Session::save_state locale '%1'\n", setlocale (LC_NUMERIC, NULL)));
|
|
|
|
|
2020-01-29 22:09:48 -05:00
|
|
|
/* only_used_assets is only possible when archiving */
|
|
|
|
assert (!only_used_assets || for_archive);
|
|
|
|
/* template and archive are exclusive */
|
|
|
|
assert (!template_only || !for_archive);
|
|
|
|
/* switch_to_snapshot needs a new name and can't be pending */
|
2020-01-30 15:05:48 -05:00
|
|
|
assert (!switch_to_snapshot || (!snapshot_name.empty () && snapshot_name != _current_snapshot_name && !pending && !template_only && !for_archive));
|
2020-01-29 22:09:48 -05:00
|
|
|
/* pending saves are for current snapshot only */
|
2020-01-30 15:05:48 -05:00
|
|
|
assert (!pending || ((snapshot_name.empty () || snapshot_name == _current_snapshot_name) && !template_only && !for_archive));
|
2020-01-29 22:09:48 -05:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
XMLTree tree;
|
2012-06-23 01:07:02 -04:00
|
|
|
std::string xml_path(_session_dir->root_path());
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2014-07-09 10:18:28 -04:00
|
|
|
/* prevent concurrent saves from different threads */
|
|
|
|
|
|
|
|
Glib::Threads::Mutex::Lock lm (save_state_lock);
|
2017-10-03 23:06:47 -04:00
|
|
|
Glib::Threads::Mutex::Lock lx (save_source_lock, Glib::Threads::NOT_LOCK);
|
|
|
|
if (!for_archive) {
|
|
|
|
lx.acquire ();
|
|
|
|
}
|
2014-07-09 10:18:28 -04:00
|
|
|
|
2019-03-18 10:33:05 -04:00
|
|
|
if (!_writable || cannot_save()) {
|
2008-06-02 17:41:35 -04:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-06-28 15:27:36 -04:00
|
|
|
if (g_atomic_int_get(&_suspend_save)) {
|
2020-01-29 22:12:19 -05:00
|
|
|
/* StateProtector cannot be used for templates or save-as */
|
2020-01-30 15:05:48 -05:00
|
|
|
assert (!template_only && !switch_to_snapshot && !for_archive && (snapshot_name.empty () || snapshot_name == _current_snapshot_name));
|
2020-01-29 22:12:19 -05:00
|
|
|
if (pending) {
|
|
|
|
_save_queued_pending = true;
|
|
|
|
} else {
|
|
|
|
_save_queued = true;
|
|
|
|
}
|
2014-06-28 15:27:36 -04:00
|
|
|
return 1;
|
|
|
|
}
|
2020-01-29 22:12:19 -05:00
|
|
|
if (pending) {
|
|
|
|
_save_queued_pending = false;
|
|
|
|
} else {
|
|
|
|
_save_queued = false;
|
|
|
|
}
|
2014-06-28 15:27:36 -04:00
|
|
|
|
2017-06-24 19:56:53 -04:00
|
|
|
snapshot_t fork_state = NormalSave;
|
2017-10-03 23:06:47 -04:00
|
|
|
if (!snapshot_name.empty() && snapshot_name != _current_snapshot_name && !template_only && !pending && !for_archive) {
|
2017-06-24 19:56:53 -04:00
|
|
|
/* snapshot, close midi */
|
|
|
|
fork_state = switch_to_snapshot ? SwitchToSnapshot : SnapshotKeep;
|
|
|
|
}
|
|
|
|
|
2016-04-08 15:45:32 -04:00
|
|
|
#ifndef NDEBUG
|
|
|
|
const int64_t save_start_time = g_get_monotonic_time();
|
|
|
|
#endif
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
/* 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 */
|
2010-05-18 23:03:28 -04:00
|
|
|
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
|
2012-01-17 21:10:40 -05:00
|
|
|
try {
|
|
|
|
i->second->session_saved();
|
|
|
|
} catch (Evoral::SMF::FileError& e) {
|
|
|
|
error << string_compose ("Could not write to MIDI file %1; MIDI data not saved.", e.file_name ()) << endmsg;
|
|
|
|
}
|
2012-05-24 02:09:29 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
PBD::Unwinder<bool> uw (LV2Plugin::force_state_save, for_archive);
|
|
|
|
|
2015-01-24 19:06:31 -05:00
|
|
|
SessionSaveUnderway (); /* EMIT SIGNAL */
|
2013-04-08 14:52:33 -04:00
|
|
|
|
2015-10-16 14:54:15 -04:00
|
|
|
bool mark_as_clean = true;
|
|
|
|
if (!snapshot_name.empty() && !switch_to_snapshot) {
|
|
|
|
mark_as_clean = false;
|
|
|
|
}
|
|
|
|
|
2015-05-07 22:35:35 -04:00
|
|
|
if (template_only) {
|
2015-10-16 14:54:15 -04:00
|
|
|
mark_as_clean = false;
|
2015-05-07 22:35:35 -04:00
|
|
|
tree.set_root (&get_template());
|
|
|
|
} else {
|
2017-10-03 23:06:47 -04:00
|
|
|
tree.set_root (&state (false, fork_state, only_used_assets));
|
2015-05-07 22:35:35 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (snapshot_name.empty()) {
|
|
|
|
snapshot_name = _current_snapshot_name;
|
2010-04-20 22:24:38 -04:00
|
|
|
} else if (switch_to_snapshot) {
|
2015-11-19 21:10:57 -05:00
|
|
|
set_snapshot_name (snapshot_name);
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2015-10-16 14:54:15 -04:00
|
|
|
assert (!snapshot_name.empty());
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (!pending) {
|
|
|
|
|
|
|
|
/* proper save: use statefile_suffix (.ardour in English) */
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2012-06-23 01:07:02 -04:00
|
|
|
xml_path = Glib::build_filename (xml_path, legalize_for_path (snapshot_name) + statefile_suffix);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
/* make a backup copy of the old file */
|
|
|
|
|
2012-06-23 01:09:00 -04:00
|
|
|
if (Glib::file_test (xml_path, Glib::FILE_TEST_EXISTS) && !create_backup_file (xml_path)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
// create_backup_file will log the error
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
2019-08-15 23:31:50 -04:00
|
|
|
assert (snapshot_name == _current_snapshot_name);
|
2008-06-02 17:41:35 -04:00
|
|
|
/* pending save: use pending_suffix (.pending in English) */
|
2012-06-23 01:07:02 -04:00
|
|
|
xml_path = Glib::build_filename (xml_path, legalize_for_path (snapshot_name) + pending_suffix);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2012-06-23 01:09:31 -04:00
|
|
|
std::string tmp_path(_session_dir->root_path());
|
|
|
|
tmp_path = Glib::build_filename (tmp_path, legalize_for_path (snapshot_name) + temp_suffix);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-10-02 19:06:07 -04:00
|
|
|
#ifndef NDEBUG
|
2015-01-12 12:57:05 -05:00
|
|
|
cerr << "actually writing state to " << tmp_path << endl;
|
2017-10-02 19:06:07 -04:00
|
|
|
#endif
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2012-06-23 01:09:31 -04:00
|
|
|
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) {
|
2013-01-09 09:33:10 -05:00
|
|
|
error << string_compose(_("Could not remove temporary session file at path \"%1\" (%2)"),
|
2012-06-23 01:09:31 -04:00
|
|
|
tmp_path, g_strerror (errno)) << endmsg;
|
2012-06-23 01:08:33 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2017-10-02 19:06:07 -04:00
|
|
|
#ifndef NDEBUG
|
2015-01-12 12:57:05 -05:00
|
|
|
cerr << "renaming state to " << xml_path << endl;
|
2017-10-02 19:06:07 -04:00
|
|
|
#endif
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-07-13 17:33:30 -04:00
|
|
|
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;
|
2012-06-23 01:09:31 -04:00
|
|
|
if (g_remove (tmp_path.c_str()) != 0) {
|
2013-01-09 09:33:10 -05:00
|
|
|
error << string_compose(_("Could not remove temporary session file at path \"%1\" (%2)"),
|
2012-06-23 01:09:31 -04:00
|
|
|
tmp_path, g_strerror (errno)) << endmsg;
|
2012-06-23 01:08:33 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-14 13:46:49 -04:00
|
|
|
//Mixbus auto-backup mechanism
|
|
|
|
if(Profile->get_mixbus()) {
|
|
|
|
if (pending) { //"pending" save means it's a backup, or some other non-user-initiated save; a good time to make a backup
|
|
|
|
// make a serialized safety backup
|
|
|
|
// (will make one periodically but only one per hour is left on disk)
|
|
|
|
// these backup files go into a separated folder
|
|
|
|
char timebuf[128];
|
|
|
|
time_t n;
|
|
|
|
struct tm local_time;
|
|
|
|
time (&n);
|
|
|
|
localtime_r (&n, &local_time);
|
|
|
|
strftime (timebuf, sizeof(timebuf), "%y-%m-%d.%H", &local_time);
|
|
|
|
std::string save_path(session_directory().backup_path());
|
|
|
|
save_path += G_DIR_SEPARATOR;
|
|
|
|
save_path += legalize_for_path(_current_snapshot_name);
|
|
|
|
save_path += "-";
|
|
|
|
save_path += timebuf;
|
|
|
|
save_path += statefile_suffix;
|
|
|
|
if ( !tree.write (save_path) )
|
|
|
|
error << string_compose(_("Could not save backup file at path \"%1\" (%2)"),
|
|
|
|
save_path, g_strerror (errno)) << endmsg;
|
|
|
|
}
|
|
|
|
|
|
|
|
StateSaved (snapshot_name); /* EMIT SIGNAL */
|
|
|
|
}
|
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
if (!pending && !for_archive) {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
save_history (snapshot_name);
|
|
|
|
|
2015-10-16 14:54:15 -04:00
|
|
|
if (mark_as_clean) {
|
2019-03-18 10:33:05 -04:00
|
|
|
unset_dirty (/* EMIT SIGNAL */ true);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
StateSaved (snapshot_name); /* EMIT SIGNAL */
|
|
|
|
}
|
|
|
|
|
2016-04-08 15:45:32 -04:00
|
|
|
#ifndef NDEBUG
|
|
|
|
const int64_t elapsed_time_us = g_get_monotonic_time() - save_start_time;
|
2016-04-08 15:50:02 -04:00
|
|
|
cerr << "saved state in " << fixed << setprecision (1) << elapsed_time_us / 1000. << " ms\n";
|
2016-04-08 15:45:32 -04:00
|
|
|
#endif
|
2019-08-15 23:31:50 -04:00
|
|
|
|
|
|
|
if (!pending && !for_archive && ! template_only) {
|
|
|
|
remove_pending_capture_state ();
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
Session::restore_state (string snapshot_name)
|
|
|
|
{
|
2017-08-17 13:28:14 -04:00
|
|
|
try {
|
|
|
|
if (load_state (snapshot_name) == 0) {
|
|
|
|
set_state (*state_tree->root(), Stateful::loading_state_version);
|
|
|
|
}
|
|
|
|
} catch (...) {
|
|
|
|
// SessionException
|
|
|
|
// unknown_enumeration
|
|
|
|
return -1;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2020-04-01 16:22:37 -04:00
|
|
|
Session::load_state (string snapshot_name, bool from_template)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2008-12-18 14:31:00 -05:00
|
|
|
delete state_tree;
|
|
|
|
state_tree = 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
state_was_pending = false;
|
|
|
|
|
|
|
|
/* check for leftover pending state from a crashed capture attempt */
|
|
|
|
|
2012-06-23 01:09:33 -04:00
|
|
|
std::string xmlpath(_session_dir->root_path());
|
|
|
|
xmlpath = Glib::build_filename (xmlpath, legalize_for_path (snapshot_name) + pending_suffix);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:09:33 -04:00
|
|
|
if (Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
/* there is pending state from a crashed capture attempt */
|
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
boost::optional<int> r = AskAboutPendingState();
|
2019-11-21 11:48:56 -05:00
|
|
|
if (r.value_or (1)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
state_was_pending = true;
|
2009-10-14 12:10:01 -04:00
|
|
|
}
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (!state_was_pending) {
|
2012-06-23 01:09:33 -04:00
|
|
|
xmlpath = Glib::build_filename (_session_dir->root_path(), snapshot_name);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2012-06-23 01:09:33 -04:00
|
|
|
if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
xmlpath = Glib::build_filename (_session_dir->root_path(), legalize_for_path (snapshot_name) + statefile_suffix);
|
|
|
|
if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
|
2017-04-20 17:47:39 -04:00
|
|
|
error << string_compose(_("%1: session file \"%2\" doesn't exist!"), _name, xmlpath) << endmsg;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
state_tree = new XMLTree;
|
|
|
|
|
|
|
|
set_dirty();
|
|
|
|
|
2014-03-21 11:06:59 -04:00
|
|
|
_writable = exists_and_writable (xmlpath) && exists_and_writable(Glib::path_get_dirname(xmlpath));
|
2009-10-13 16:43:28 -04:00
|
|
|
|
2012-06-23 01:09:33 -04:00
|
|
|
if (!state_tree->read (xmlpath)) {
|
2013-01-09 09:33:10 -05:00
|
|
|
error << string_compose(_("Could not understand session file %1"), xmlpath) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
delete state_tree;
|
|
|
|
state_tree = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-05-04 23:09:37 -04:00
|
|
|
XMLNode const & root (*state_tree->root());
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (root.name() != X_("Session")) {
|
2012-06-23 01:09:33 -04:00
|
|
|
error << string_compose (_("Session file %1 is not a session"), xmlpath) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
delete state_tree;
|
|
|
|
state_tree = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
std::string version;
|
2017-08-17 12:32:49 -04:00
|
|
|
root.get_property ("version", version);
|
|
|
|
Stateful::loading_state_version = parse_stateful_loading_version (version);
|
|
|
|
|
|
|
|
if ((Stateful::loading_state_version / 1000L) > (CURRENT_SESSION_FILE_VERSION / 1000L)) {
|
|
|
|
cerr << "Session-version: " << Stateful::loading_state_version << " is not supported. Current: " << CURRENT_SESSION_FILE_VERSION << "\n";
|
2020-04-16 18:02:38 -04:00
|
|
|
throw SessionException (string_compose (_("Incompatible Session Version. That session was created with a newer version of %1"), PROGRAM_NAME));
|
2009-10-15 14:56:11 -04:00
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2020-04-01 16:22:37 -04:00
|
|
|
if (Stateful::loading_state_version < CURRENT_SESSION_FILE_VERSION && _writable && !from_template) {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:09:33 -04:00
|
|
|
std::string backup_path(_session_dir->root_path());
|
|
|
|
std::string backup_filename = string_compose ("%1-%2%3", legalize_for_path (snapshot_name), Stateful::loading_state_version, statefile_suffix);
|
|
|
|
backup_path = Glib::build_filename (backup_path, backup_filename);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-04-19 12:49:35 -04:00
|
|
|
// only create a backup for a given statefile version once
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:09:33 -04:00
|
|
|
if (!Glib::file_test (backup_path, Glib::FILE_TEST_EXISTS)) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2013-03-04 16:57:29 -05:00
|
|
|
VersionMismatch (xmlpath, backup_path);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2012-06-23 01:09:33 -04:00
|
|
|
if (!copy_file (xmlpath, backup_path)) {;
|
2012-04-19 12:49:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-19 21:10:57 -05:00
|
|
|
save_snapshot_name (snapshot_name);
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
Session::load_options (const XMLNode& node)
|
|
|
|
{
|
2011-12-29 17:14:15 -05:00
|
|
|
config.set_variables (node);
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-06-29 09:45:08 -04:00
|
|
|
bool
|
|
|
|
Session::save_default_options ()
|
|
|
|
{
|
|
|
|
return config.save_state();
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
XMLNode&
|
2017-10-03 18:35:29 -04:00
|
|
|
Session::get_state ()
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2017-10-03 18:35:29 -04:00
|
|
|
/* this is not directly called, but required by PBD::Stateful */
|
|
|
|
assert (0);
|
|
|
|
return state (false, NormalSave);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode&
|
2017-10-03 18:35:29 -04:00
|
|
|
Session::get_template ()
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
/* if we don't disable rec-enable, diskstreams
|
|
|
|
will believe they need to store their capture
|
2009-10-14 12:10:01 -04:00
|
|
|
sources in their state node.
|
2008-06-02 17:41:35 -04:00
|
|
|
*/
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
disable_record (false);
|
|
|
|
|
2017-10-03 18:35:29 -04:00
|
|
|
return state (true, NormalSave);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2017-01-07 19:23:01 -05:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
static void
|
|
|
|
merge_all_sources (boost::shared_ptr<const Playlist> pl, std::set<boost::shared_ptr<Source> >* all_sources)
|
|
|
|
{
|
|
|
|
pl->deep_sources (*all_sources);
|
|
|
|
}
|
|
|
|
|
2017-04-21 06:33:05 -04:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct route_id_compare {
|
|
|
|
bool
|
|
|
|
operator() (const boost::shared_ptr<Route>& r1, const boost::shared_ptr<Route>& r2)
|
|
|
|
{
|
|
|
|
return r1->id () < r2->id ();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} // anon namespace
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
XMLNode&
|
2017-10-03 23:06:47 -04:00
|
|
|
Session::state (bool save_template, snapshot_t snapshot_type, bool only_used_assets)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2016-05-07 11:16:59 -04:00
|
|
|
LocaleGuard lg;
|
2008-06-02 17:41:35 -04:00
|
|
|
XMLNode* node = new XMLNode("Session");
|
|
|
|
XMLNode* child;
|
|
|
|
|
2017-10-03 18:35:29 -04:00
|
|
|
PBD::Unwinder<bool> uw (Automatable::skip_saving_automation, save_template);
|
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
node->set_property("version", CURRENT_SESSION_FILE_VERSION);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2016-06-01 16:27:55 -04:00
|
|
|
child = node->add_child ("ProgramVersion");
|
2016-08-28 07:12:16 -04:00
|
|
|
child->set_property("created-with", created_with);
|
2016-06-01 16:27:55 -04:00
|
|
|
|
|
|
|
std::string modified_with = string_compose ("%1 %2", PROGRAM_NAME, revision);
|
2016-08-28 07:12:16 -04:00
|
|
|
child->set_property("modified-with", modified_with);
|
2016-06-01 16:27:55 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
/* store configuration settings */
|
|
|
|
|
2017-10-03 18:35:29 -04:00
|
|
|
if (!save_template) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
node->set_property ("name", _name);
|
2017-09-18 12:39:17 -04:00
|
|
|
node->set_property ("sample-rate", _base_sample_rate);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (session_dirs.size() > 1) {
|
|
|
|
|
|
|
|
string p;
|
|
|
|
|
|
|
|
vector<space_and_path>::iterator i = session_dirs.begin();
|
|
|
|
vector<space_and_path>::iterator next;
|
|
|
|
|
|
|
|
++i; /* skip the first one */
|
|
|
|
next = i;
|
|
|
|
++next;
|
|
|
|
|
|
|
|
while (i != session_dirs.end()) {
|
|
|
|
|
|
|
|
p += (*i).path;
|
|
|
|
|
|
|
|
if (next != session_dirs.end()) {
|
2014-04-11 10:36:04 -04:00
|
|
|
p += G_SEARCHPATH_SEPARATOR;
|
2008-06-02 17:41:35 -04:00
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
++next;
|
|
|
|
++i;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
child = node->add_child ("Path");
|
|
|
|
child->add_content (p);
|
|
|
|
}
|
2019-02-06 10:54:13 -05:00
|
|
|
node->set_property ("end-is-free", _session_range_is_free); //deprecated, but keep storing this value for compatibility with prior v5.
|
|
|
|
node->set_property ("session-range-is-free", _session_range_is_free);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* save the ID counter */
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
node->set_property ("id-counter", ID::counter());
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
node->set_property ("name-counter", name_id_counter ());
|
2016-04-12 07:49:50 -04:00
|
|
|
|
2010-07-20 12:27:34 -04:00
|
|
|
/* save the event ID counter */
|
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
node->set_property ("event-counter", Evoral::event_id_counter());
|
2010-07-20 12:27:34 -04:00
|
|
|
|
2016-03-07 16:25:50 -05:00
|
|
|
/* save the VCA counter */
|
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
node->set_property ("vca-counter", VCA::get_next_vca_number());
|
2016-03-07 16:25:50 -05:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
/* various options */
|
|
|
|
|
2013-09-05 13:22:34 -04:00
|
|
|
list<XMLNode*> midi_port_nodes = _midi_ports->get_midi_port_states();
|
|
|
|
if (!midi_port_nodes.empty()) {
|
|
|
|
XMLNode* midi_port_stuff = new XMLNode ("MIDIPorts");
|
|
|
|
for (list<XMLNode*>::const_iterator n = midi_port_nodes.begin(); n != midi_port_nodes.end(); ++n) {
|
|
|
|
midi_port_stuff->add_child_nocopy (**n);
|
|
|
|
}
|
|
|
|
node->add_child_nocopy (*midi_port_stuff);
|
|
|
|
}
|
|
|
|
|
2016-09-29 18:11:24 -04:00
|
|
|
XMLNode& cfgxml (config.get_variables ());
|
2017-10-03 18:35:29 -04:00
|
|
|
if (save_template) {
|
2016-09-29 18:11:24 -04:00
|
|
|
/* 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);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-04-18 13:46:29 -04:00
|
|
|
node->add_child_nocopy (ARDOUR::SessionMetadata::Metadata()->get_state());
|
2008-09-17 04:44:51 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
child = node->add_child ("Sources");
|
|
|
|
|
2017-10-03 18:35:29 -04:00
|
|
|
if (!save_template) {
|
2012-07-25 13:48:55 -04:00
|
|
|
Glib::Threads::Mutex::Lock sl (source_lock);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
|
|
|
|
|
|
|
|
if (only_used_assets) {
|
2019-03-19 00:14:00 -04:00
|
|
|
_playlists->sync_all_regions_with_regions ();
|
|
|
|
_playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot), false);
|
2017-10-03 23:06:47 -04:00
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
for (SourceMap::iterator siter = sources.begin(); siter != sources.end(); ++siter) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2011-05-16 16:16:57 -04:00
|
|
|
/* Don't save information about non-file Sources, or
|
2020-02-25 23:35:07 -05:00
|
|
|
* about file sources that are empty
|
2011-05-16 16:16:57 -04:00
|
|
|
* and unused by any regions.
|
2017-04-20 17:47:39 -04:00
|
|
|
*/
|
2010-06-23 16:14:07 -04:00
|
|
|
boost::shared_ptr<FileSource> fs;
|
2011-05-16 16:16:57 -04:00
|
|
|
|
2017-06-24 19:56:53 -04:00
|
|
|
if ((fs = boost::dynamic_pointer_cast<FileSource> (siter->second)) == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-02-25 23:35:07 -05:00
|
|
|
if (fs->empty() && !fs->used()) {
|
|
|
|
continue;
|
2017-06-24 19:56:53 -04:00
|
|
|
}
|
2011-05-16 16:16:57 -04:00
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
if (only_used_assets) {
|
|
|
|
/* skip only unused audio files */
|
|
|
|
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (fs);
|
|
|
|
if (afs && !afs->used()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (afs && sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-24 19:56:53 -04:00
|
|
|
if (snapshot_type != NormalSave && fs->within_session ()) {
|
|
|
|
/* copy MIDI sources to new file
|
|
|
|
*
|
|
|
|
* We cannot replace the midi-source and MidiRegion::clobber_sources,
|
|
|
|
* because the GUI (midi_region) has a direct pointer to the midi-model
|
|
|
|
* of the source, as does UndoTransaction.
|
|
|
|
*
|
|
|
|
* On the upside, .mid files are not kept open. The file is only open
|
|
|
|
* when reading the model initially and when flushing the model to disk:
|
|
|
|
* source->session_saved () or export.
|
|
|
|
*
|
|
|
|
* We can change the _path of the existing source under the hood, keeping
|
|
|
|
* all IDs, references and pointers intact.
|
|
|
|
* */
|
|
|
|
boost::shared_ptr<SMFSource> ms;
|
|
|
|
if ((ms = boost::dynamic_pointer_cast<SMFSource> (siter->second)) != 0) {
|
|
|
|
const std::string ancestor_name = ms->ancestor_name();
|
|
|
|
const std::string base = PBD::basename_nosuffix(ancestor_name);
|
|
|
|
const string path = new_midi_source_path (base, false);
|
|
|
|
|
|
|
|
/* use SMF-API to clone data (use the midi_model, not data on disk) */
|
|
|
|
boost::shared_ptr<SMFSource> newsrc (new SMFSource (*this, path, SndFileSource::default_writable_flags));
|
|
|
|
Source::Lock lm (ms->mutex());
|
|
|
|
|
|
|
|
// TODO special-case empty, removable() files: just create a new removable.
|
|
|
|
// (load + write flushes the model and creates the file)
|
|
|
|
if (!ms->model()) {
|
|
|
|
ms->load_model (lm);
|
|
|
|
}
|
2017-09-24 12:03:54 -04:00
|
|
|
if (ms->write_to (lm, newsrc, Temporal::Beats(), std::numeric_limits<Temporal::Beats>::max())) {
|
2017-06-24 19:56:53 -04:00
|
|
|
error << string_compose (_("Session-Save: Failed to copy MIDI Source '%1' for snapshot"), ancestor_name) << endmsg;
|
|
|
|
} else {
|
|
|
|
if (snapshot_type == SnapshotKeep) {
|
|
|
|
/* keep working on current session.
|
|
|
|
*
|
|
|
|
* Save snapshot-state with the original filename.
|
|
|
|
* Switch to use new path for future saves of the main session.
|
|
|
|
*/
|
|
|
|
child->add_child_nocopy (ms->get_state());
|
|
|
|
}
|
|
|
|
|
|
|
|
/* swap file-paths.
|
|
|
|
* ~SMFSource unlinks removable() files.
|
|
|
|
*/
|
|
|
|
std::string npath (ms->path ());
|
|
|
|
ms->replace_file (newsrc->path ());
|
|
|
|
newsrc->replace_file (npath);
|
|
|
|
|
|
|
|
if (snapshot_type == SwitchToSnapshot) {
|
|
|
|
/* save and switch to snapshot.
|
|
|
|
*
|
|
|
|
* Leave the old file in place (as is).
|
|
|
|
* Snapshot uses new source directly
|
|
|
|
*/
|
|
|
|
child->add_child_nocopy (ms->get_state());
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-24 19:56:53 -04:00
|
|
|
|
|
|
|
child->add_child_nocopy (siter->second->get_state());
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
child = node->add_child ("Regions");
|
|
|
|
|
2017-10-03 18:35:29 -04:00
|
|
|
if (!save_template) {
|
2012-07-25 13:48:55 -04:00
|
|
|
Glib::Threads::Mutex::Lock rl (region_lock);
|
2017-10-03 23:06:47 -04:00
|
|
|
|
|
|
|
if (!only_used_assets) {
|
|
|
|
const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
|
|
|
|
for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
|
|
|
|
boost::shared_ptr<Region> r = i->second;
|
2020-02-28 00:35:33 -05:00
|
|
|
/* regions must have sources */
|
|
|
|
assert (r->sources().size() > 0 && r->master_sources().size() > 0);
|
2017-10-03 23:06:47 -04:00
|
|
|
/* only store regions not attached to playlists */
|
|
|
|
if (r->playlist() == 0) {
|
|
|
|
if (boost::dynamic_pointer_cast<AudioRegion>(r)) {
|
|
|
|
child->add_child_nocopy ((boost::dynamic_pointer_cast<AudioRegion>(r))->get_basic_state ());
|
|
|
|
} else {
|
|
|
|
child->add_child_nocopy (r->get_state ());
|
|
|
|
}
|
2013-03-03 03:26:50 -05:00
|
|
|
}
|
2017-04-20 17:47:39 -04:00
|
|
|
}
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-05-22 12:11:00 -04:00
|
|
|
RegionFactory::CompoundAssociations& cassocs (RegionFactory::compound_associations());
|
|
|
|
|
|
|
|
if (!cassocs.empty()) {
|
|
|
|
XMLNode* ca = node->add_child (X_("CompoundAssociations"));
|
|
|
|
|
|
|
|
for (RegionFactory::CompoundAssociations::iterator i = cassocs.begin(); i != cassocs.end(); ++i) {
|
2017-10-03 23:06:47 -04:00
|
|
|
if (i->first->playlist () == 0 && only_used_assets) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-05-22 12:11:00 -04:00
|
|
|
XMLNode* can = new XMLNode (X_("CompoundAssociation"));
|
2016-08-28 07:12:16 -04:00
|
|
|
can->set_property (X_("copy"), i->first->id());
|
|
|
|
can->set_property (X_("original"), i->second->id());
|
2011-05-22 12:11:00 -04:00
|
|
|
ca->add_child_nocopy (*can);
|
2017-10-03 23:06:47 -04:00
|
|
|
/* see above, child is still "Regions" here */
|
|
|
|
if (i->second->playlist() == 0 && only_used_assets) {
|
|
|
|
if (boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>( i->second)) {
|
|
|
|
child->add_child_nocopy (ar->get_basic_state ());
|
|
|
|
} else {
|
|
|
|
child->add_child_nocopy (ar->get_state ());
|
|
|
|
}
|
|
|
|
}
|
2011-05-22 12:11:00 -04:00
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2017-10-03 18:35:29 -04:00
|
|
|
if (!save_template) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2017-05-05 07:31:21 -04:00
|
|
|
node->add_child_nocopy (_selection->get_state());
|
|
|
|
|
2015-04-02 17:25:56 -04:00
|
|
|
if (_locations) {
|
2015-10-04 12:46:27 -04:00
|
|
|
node->add_child_nocopy (_locations->get_state());
|
2015-04-02 17:25:56 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
} else {
|
2015-04-02 17:25:56 -04:00
|
|
|
Locations loc (*this);
|
2017-02-09 06:22:59 -05:00
|
|
|
const bool was_dirty = dirty();
|
2008-06-02 17:41:35 -04:00
|
|
|
// for a template, just create a new Locations, populate it
|
|
|
|
// with the default start and end, and get the state for that.
|
2016-12-28 09:39:31 -05:00
|
|
|
Location* range = new Location (*this, 0, 0, _("session"), Location::IsSessionRange, 0);
|
2017-09-18 12:39:17 -04:00
|
|
|
range->set (max_samplepos, 0);
|
2010-04-18 17:29:48 -04:00
|
|
|
loc.add (range);
|
2015-04-02 17:25:56 -04:00
|
|
|
XMLNode& locations_state = loc.get_state();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-04-02 17:25:56 -04:00
|
|
|
node->add_child_nocopy (locations_state);
|
2017-02-09 06:22:59 -05:00
|
|
|
|
|
|
|
/* 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) {
|
2019-03-18 10:33:05 -04:00
|
|
|
unset_dirty ();
|
2017-02-09 06:22:59 -05:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
child = node->add_child ("Bundles");
|
|
|
|
{
|
2009-01-25 01:47:11 -05:00
|
|
|
boost::shared_ptr<BundleList> bundles = _bundles.reader ();
|
|
|
|
for (BundleList::iterator i = bundles->begin(); i != bundles->end(); ++i) {
|
2008-06-02 17:41:35 -04:00
|
|
|
boost::shared_ptr<UserBundle> b = boost::dynamic_pointer_cast<UserBundle> (*i);
|
|
|
|
if (b) {
|
|
|
|
child->add_child_nocopy (b->get_state());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-29 16:16:31 -05:00
|
|
|
node->add_child_nocopy (_vca_manager->get_state());
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
child = node->add_child ("Routes");
|
|
|
|
{
|
|
|
|
boost::shared_ptr<RouteList> r = routes.reader ();
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2017-04-21 06:33:05 -04:00
|
|
|
route_id_compare cmp;
|
|
|
|
RouteList xml_node_order (*r);
|
|
|
|
xml_node_order.sort (cmp);
|
2010-03-10 12:31:16 -05:00
|
|
|
|
2017-04-26 03:33:55 -04:00
|
|
|
for (RouteList::const_iterator i = xml_node_order.begin(); i != xml_node_order.end(); ++i) {
|
2013-04-06 16:04:02 -04:00
|
|
|
if (!(*i)->is_auditioner()) {
|
2017-10-03 18:35:29 -04:00
|
|
|
if (save_template) {
|
2008-06-02 17:41:35 -04:00
|
|
|
child->add_child_nocopy ((*i)->get_template());
|
2017-10-03 18:35:29 -04:00
|
|
|
} else {
|
|
|
|
child->add_child_nocopy ((*i)->get_state());
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-19 00:14:00 -04:00
|
|
|
_playlists->add_state (node, save_template, !only_used_assets);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2009-06-21 15:59:56 -04:00
|
|
|
child = node->add_child ("RouteGroups");
|
|
|
|
for (list<RouteGroup *>::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) {
|
2008-06-02 17:41:35 -04:00
|
|
|
child->add_child_nocopy ((*i)->get_state());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_click_io) {
|
2012-01-27 17:47:16 -05:00
|
|
|
XMLNode* gain_child = node->add_child ("Click");
|
2017-10-03 18:35:29 -04:00
|
|
|
gain_child->add_child_nocopy (_click_io->get_state ());
|
|
|
|
gain_child->add_child_nocopy (_click_gain->get_state ());
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
node->add_child_nocopy (_speakers->get_state());
|
2008-06-02 17:41:35 -04:00
|
|
|
node->add_child_nocopy (_tempo_map->get_state());
|
|
|
|
node->add_child_nocopy (get_control_protocol_state());
|
|
|
|
|
|
|
|
if (_extra_xml) {
|
|
|
|
node->add_child_copy (*_extra_xml);
|
|
|
|
}
|
|
|
|
|
2016-02-23 09:41:21 -05:00
|
|
|
{
|
|
|
|
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"));
|
2016-08-28 07:12:16 -04:00
|
|
|
script_node->set_property (X_("lua"), LUA_VERSION);
|
2016-02-23 09:41:21 -05:00
|
|
|
script_node->add_content (b64s);
|
|
|
|
node->add_child_nocopy (*script_node);
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return *node;
|
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode&
|
|
|
|
Session::get_control_protocol_state ()
|
|
|
|
{
|
2018-09-18 18:51:59 -04:00
|
|
|
return ControlProtocolManager::instance().get_state ();
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2009-10-14 20:57:55 -04:00
|
|
|
Session::set_state (const XMLNode& node, int version)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2016-05-07 11:16:59 -04:00
|
|
|
LocaleGuard lg;
|
2008-06-02 17:41:35 -04:00
|
|
|
XMLNodeList nlist;
|
|
|
|
XMLNode* child;
|
|
|
|
int ret = -1;
|
|
|
|
|
2019-03-18 10:33:05 -04:00
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state | CannotSave);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2011-02-17 09:05:21 -05:00
|
|
|
if (node.name() != X_("Session")) {
|
2008-06-02 17:41:35 -04:00
|
|
|
fatal << _("programming error: Session: incorrect XML node sent to set_state()") << endmsg;
|
2014-09-09 23:18:09 -04:00
|
|
|
goto out;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
node.get_property ("name", _name);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-09-18 12:39:17 -04:00
|
|
|
if (node.get_property (X_("sample-rate"), _base_sample_rate)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-09-18 12:39:17 -04:00
|
|
|
_nominal_sample_rate = _base_sample_rate;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-04-18 09:16:27 -04:00
|
|
|
assert (AudioEngine::instance()->running ());
|
2017-09-18 12:39:17 -04:00
|
|
|
if (_base_sample_rate != AudioEngine::instance()->sample_rate ()) {
|
|
|
|
boost::optional<int> r = AskAboutSampleRateMismatch (_base_sample_rate, _current_sample_rate);
|
2019-11-21 11:48:56 -05:00
|
|
|
if (r.value_or (0)) {
|
2014-09-09 23:18:09 -04:00
|
|
|
goto out;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-01 16:27:55 -04:00
|
|
|
created_with = "unknown";
|
|
|
|
if ((child = find_named_node (node, "ProgramVersion")) != 0) {
|
2016-08-28 07:12:16 -04:00
|
|
|
child->get_property (X_("created-with"), created_with);
|
2016-06-01 16:27:55 -04:00
|
|
|
}
|
|
|
|
|
2012-06-23 01:06:54 -04:00
|
|
|
setup_raid_path(_session_dir->root_path());
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2019-02-06 10:54:13 -05:00
|
|
|
node.get_property (X_("end-is-free"), _session_range_is_free); //deprectated, but use old values if they are in the config
|
|
|
|
|
|
|
|
node.get_property (X_("session-range-is-free"), _session_range_is_free);
|
2016-07-12 11:19:15 -04:00
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
uint64_t counter;
|
|
|
|
if (node.get_property (X_("id-counter"), counter)) {
|
|
|
|
ID::init_counter (counter);
|
2008-06-02 17:41:35 -04:00
|
|
|
} else {
|
|
|
|
/* old sessions used a timebased counter, so fake
|
2017-04-20 17:47:39 -04:00
|
|
|
* the startup ID counter based on a standard
|
|
|
|
* timestamp.
|
|
|
|
*/
|
2008-06-02 17:41:35 -04:00
|
|
|
time_t now;
|
|
|
|
time (&now);
|
|
|
|
ID::init_counter (now);
|
|
|
|
}
|
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
if (node.get_property (X_("name-counter"), counter)) {
|
|
|
|
init_name_id_counter (counter);
|
2016-04-12 07:49:50 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
if (node.get_property (X_("event-counter"), counter)) {
|
|
|
|
Evoral::init_event_id_counter (counter);
|
2016-04-12 07:49:50 -04:00
|
|
|
}
|
2013-09-05 13:22:34 -04:00
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
if (node.get_property (X_("vca-counter"), counter)) {
|
|
|
|
VCA::set_next_vca_number (counter);
|
2016-03-07 16:25:50 -05:00
|
|
|
} else {
|
|
|
|
VCA::set_next_vca_number (1);
|
|
|
|
}
|
|
|
|
|
2013-09-05 13:22:34 -04:00
|
|
|
if ((child = find_named_node (node, "MIDIPorts")) != 0) {
|
|
|
|
_midi_ports->set_midi_port_states (child->children());
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
IO::disable_connecting ();
|
|
|
|
|
2011-06-11 11:35:34 -04:00
|
|
|
Stateful::save_extra_xml (node);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (((child = find_named_node (node, "Options")) != 0)) { /* old style */
|
|
|
|
load_options (*child);
|
|
|
|
} else if ((child = find_named_node (node, "Config")) != 0) { /* new style */
|
|
|
|
load_options (*child);
|
|
|
|
} else {
|
|
|
|
error << _("Session: XML state has no options section") << endmsg;
|
|
|
|
}
|
|
|
|
|
2009-10-14 20:57:55 -04:00
|
|
|
if (version >= 3000) {
|
|
|
|
if ((child = find_named_node (node, "Metadata")) == 0) {
|
|
|
|
warning << _("Session: XML state has no metadata section") << endmsg;
|
2012-04-18 13:46:29 -04:00
|
|
|
} else if ( ARDOUR::SessionMetadata::Metadata()->set_state (*child, version) ) {
|
2009-10-14 20:57:55 -04:00
|
|
|
goto out;
|
|
|
|
}
|
2008-09-17 04:44:51 -04:00
|
|
|
}
|
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if ((child = find_named_node (node, X_("Speakers"))) != 0) {
|
|
|
|
_speakers->set_state (*child, version);
|
|
|
|
}
|
2011-09-14 13:16:54 -04:00
|
|
|
|
|
|
|
if ((child = find_named_node (node, "Sources")) == 0) {
|
|
|
|
error << _("Session: XML state has no sources section") << endmsg;
|
|
|
|
goto out;
|
|
|
|
} else if (load_sources (*child)) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((child = find_named_node (node, "TempoMap")) == 0) {
|
|
|
|
error << _("Session: XML state has no Tempo Map section") << endmsg;
|
|
|
|
goto out;
|
|
|
|
} else if (_tempo_map->set_state (*child, version)) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if ((child = find_named_node (node, "Locations")) == 0) {
|
|
|
|
error << _("Session: XML state has no locations section") << endmsg;
|
|
|
|
goto out;
|
2010-08-09 12:40:31 -04:00
|
|
|
} else if (_locations->set_state (*child, version)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2014-09-17 12:28:09 -04:00
|
|
|
locations_changed ();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2010-05-09 16:48:21 -04:00
|
|
|
if (_session_range_location) {
|
|
|
|
AudioFileSource::set_header_position_offset (_session_range_location->start());
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if ((child = find_named_node (node, "Regions")) == 0) {
|
|
|
|
error << _("Session: XML state has no Regions section") << endmsg;
|
|
|
|
goto out;
|
|
|
|
} else if (load_regions (*child)) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((child = find_named_node (node, "Playlists")) == 0) {
|
|
|
|
error << _("Session: XML state has no playlists section") << endmsg;
|
|
|
|
goto out;
|
2019-03-19 00:14:00 -04:00
|
|
|
} else if (_playlists->load (*this, *child)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((child = find_named_node (node, "UnusedPlaylists")) == 0) {
|
|
|
|
// this is OK
|
2019-03-19 00:14:00 -04:00
|
|
|
} else if (_playlists->load_unused (*this, *child)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
goto out;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-05-22 12:11:00 -04:00
|
|
|
if ((child = find_named_node (node, "CompoundAssociations")) != 0) {
|
|
|
|
if (load_compounds (*child)) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2009-10-14 20:57:55 -04:00
|
|
|
if (version >= 3000) {
|
|
|
|
if ((child = find_named_node (node, "Bundles")) == 0) {
|
|
|
|
warning << _("Session: XML state has no bundles section") << endmsg;
|
|
|
|
//goto out;
|
|
|
|
} else {
|
|
|
|
/* We can't load Bundles yet as they need to be able
|
2017-04-20 17:47:39 -04:00
|
|
|
* to convert from port names to Port objects, which can't happen until
|
|
|
|
* later */
|
2009-10-14 20:57:55 -04:00
|
|
|
_bundle_xml_node = new XMLNode (*child);
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2016-02-29 16:16:31 -05:00
|
|
|
if ((child = find_named_node (node, VCAManager::xml_node_name)) != 0) {
|
|
|
|
_vca_manager->set_state (*child, version);
|
|
|
|
}
|
|
|
|
|
2019-12-16 18:34:26 -05:00
|
|
|
if (version < 3000) {
|
|
|
|
if ((child = find_named_node (node, "DiskStreams"))) {
|
|
|
|
for (XMLNodeList::const_iterator n = child->children ().begin (); n != child->children ().end (); ++n) {
|
|
|
|
if ((*n)->name() == "AudioDiskstream" || (*n)->name() == "DiskStream") {
|
|
|
|
std::string diskstream_id;
|
|
|
|
std::string playlist_name;
|
|
|
|
if ((*n)->get_property ("playlist", playlist_name) && (*n)->get_property ("id", diskstream_id)) {
|
|
|
|
_diskstreams_2X [PBD::ID(diskstream_id)] = playlist_name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-09 22:25:32 -05:00
|
|
|
if ((child = find_named_node (node, "Routes")) == 0) {
|
|
|
|
error << _("Session: XML state has no routes section") << endmsg;
|
|
|
|
goto out;
|
|
|
|
} else if (load_routes (*child, version)) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2019-03-19 16:56:13 -04:00
|
|
|
/* Now that we Tracks have been loaded and playlists are assigned */
|
|
|
|
_playlists->update_tracking ();
|
|
|
|
|
2019-12-16 18:34:26 -05:00
|
|
|
_diskstreams_2X.clear ();
|
|
|
|
|
2016-04-25 11:03:08 -04:00
|
|
|
/* Now that we have Routes and masters loaded, connect them if appropriate */
|
|
|
|
|
|
|
|
Slavable::Assign (_vca_manager); /* EMIT SIGNAL */
|
|
|
|
|
2009-10-14 20:57:55 -04:00
|
|
|
if (version >= 3000) {
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2009-10-14 20:57:55 -04:00
|
|
|
if ((child = find_named_node (node, "RouteGroups")) == 0) {
|
|
|
|
error << _("Session: XML state has no route groups section") << endmsg;
|
|
|
|
goto out;
|
|
|
|
} else if (load_route_groups (*child, version)) {
|
|
|
|
goto out;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2009-10-14 20:57:55 -04:00
|
|
|
} else if (version < 3000) {
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2009-10-14 20:57:55 -04:00
|
|
|
if ((child = find_named_node (node, "EditGroups")) == 0) {
|
|
|
|
error << _("Session: XML state has no edit groups section") << endmsg;
|
|
|
|
goto out;
|
|
|
|
} else if (load_route_groups (*child, version)) {
|
|
|
|
goto out;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2009-10-14 20:57:55 -04:00
|
|
|
if ((child = find_named_node (node, "MixGroups")) == 0) {
|
|
|
|
error << _("Session: XML state has no mix groups section") << endmsg;
|
|
|
|
goto out;
|
|
|
|
} else if (load_route_groups (*child, version)) {
|
|
|
|
goto out;
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((child = find_named_node (node, "Click")) == 0) {
|
|
|
|
warning << _("Session: XML state has no click section") << endmsg;
|
|
|
|
} else if (_click_io) {
|
2013-10-18 15:20:19 -04:00
|
|
|
setup_click_state (&node);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2012-10-17 10:36:40 -04:00
|
|
|
if ((child = find_named_node (node, ControlProtocolManager::state_node_name)) != 0) {
|
2018-10-03 20:30:21 -04:00
|
|
|
ControlProtocolManager::instance().set_state (*child, 1 /* here: session-specific state */);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2016-02-23 09:41:21 -05:00
|
|
|
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) {
|
2020-04-18 12:13:14 -04:00
|
|
|
#ifndef NDEBUG
|
2016-02-23 09:41:21 -05:00
|
|
|
cerr << "LuaException:" << e.what () << endl;
|
2020-04-18 12:13:14 -04:00
|
|
|
#endif
|
|
|
|
warning << "LuaException: " << e.what () << endmsg;
|
2017-08-18 19:05:18 -04:00
|
|
|
} catch (...) { }
|
2016-02-23 09:41:21 -05:00
|
|
|
g_free (buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-05 07:31:21 -04:00
|
|
|
if ((child = find_named_node (node, X_("Selection")))) {
|
|
|
|
_selection->set_state (*child, version);
|
|
|
|
}
|
|
|
|
|
2014-09-19 09:45:01 -04:00
|
|
|
update_route_record_state ();
|
2012-02-07 13:28:09 -05:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
/* here beginneth the second phase ... */
|
2015-11-19 21:10:57 -05:00
|
|
|
set_snapshot_name (_current_snapshot_name);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
StateReady (); /* EMIT SIGNAL */
|
|
|
|
|
2014-09-09 23:18:09 -04:00
|
|
|
delete state_tree;
|
|
|
|
state_tree = 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
out:
|
2014-09-09 23:18:09 -04:00
|
|
|
delete state_tree;
|
|
|
|
state_tree = 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2009-10-14 20:57:55 -04:00
|
|
|
Session::load_routes (const XMLNode& node, int version)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
XMLNodeList nlist;
|
|
|
|
XMLNodeConstIterator niter;
|
|
|
|
RouteList new_routes;
|
|
|
|
|
|
|
|
nlist = node.children();
|
|
|
|
|
|
|
|
set_dirty();
|
|
|
|
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
|
2010-04-21 16:42:22 -04:00
|
|
|
boost::shared_ptr<Route> route;
|
2017-08-17 10:33:38 -04:00
|
|
|
|
2010-04-21 16:42:22 -04:00
|
|
|
if (version < 3000) {
|
|
|
|
route = XMLRouteFactory_2X (**niter, version);
|
2017-08-17 10:33:38 -04:00
|
|
|
} else if (version < 5000) {
|
|
|
|
route = XMLRouteFactory_3X (**niter, version);
|
2010-04-21 16:42:22 -04:00
|
|
|
} else {
|
|
|
|
route = XMLRouteFactory (**niter, version);
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (route == 0) {
|
2009-01-03 23:43:12 -05:00
|
|
|
error << _("Session: cannot create Route from XML description.") << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
BootMessage (string_compose (_("Loaded track/bus %1"), route->name()));
|
|
|
|
|
|
|
|
new_routes.push_back (route);
|
|
|
|
}
|
|
|
|
|
2015-02-09 12:12:15 -05:00
|
|
|
BootMessage (_("Tracks/busses loaded; Adding to Session"));
|
|
|
|
|
2016-05-16 07:30:28 -04:00
|
|
|
add_routes (new_routes, false, false, false, PresentationInfo::max_order);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2015-02-09 12:12:15 -05:00
|
|
|
BootMessage (_("Finished adding tracks/busses"));
|
2015-01-08 08:38:08 -05:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::shared_ptr<Route>
|
2009-10-14 20:57:55 -04:00
|
|
|
Session::XMLRouteFactory (const XMLNode& node, int version)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2010-03-24 08:55:04 -04:00
|
|
|
boost::shared_ptr<Route> ret;
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (node.name() != "Route") {
|
2010-03-24 08:55:04 -04:00
|
|
|
return ret;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2017-04-14 12:32:16 -04:00
|
|
|
XMLProperty const * pl_prop = node.property (X_("audio-playlist"));
|
2017-04-03 17:36:12 -04:00
|
|
|
|
2017-04-14 12:32:16 -04:00
|
|
|
if (!pl_prop) {
|
|
|
|
pl_prop = node.property (X_("midi-playlist"));
|
2017-04-03 17:36:12 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
DataType type = DataType::AUDIO;
|
2016-08-28 07:12:16 -04:00
|
|
|
node.get_property("default-type", type);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2010-03-24 08:55:04 -04:00
|
|
|
assert (type != DataType::NIL);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-04-14 12:32:16 -04:00
|
|
|
if (pl_prop) {
|
|
|
|
|
|
|
|
/* has at least 1 playlist, therefore a track ... */
|
2010-03-24 23:40:07 -04:00
|
|
|
|
2011-02-22 16:15:42 -05:00
|
|
|
boost::shared_ptr<Track> track;
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (type == DataType::AUDIO) {
|
2020-03-15 09:03:24 -04:00
|
|
|
track.reset (new AudioTrack (*this));
|
2017-04-20 17:47:39 -04:00
|
|
|
} else {
|
2020-03-15 09:03:24 -04:00
|
|
|
track.reset (new MidiTrack (*this));
|
2017-04-20 17:47:39 -04:00
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (track->init()) {
|
|
|
|
return ret;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (track->set_state (node, version)) {
|
2017-08-17 10:33:38 -04:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_MARK_TRACK (track);
|
|
|
|
ret = track;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
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) {
|
|
|
|
BOOST_MARK_ROUTE (r);
|
|
|
|
ret = r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::shared_ptr<Route>
|
|
|
|
Session::XMLRouteFactory_3X (const XMLNode& node, int version)
|
|
|
|
{
|
|
|
|
boost::shared_ptr<Route> ret;
|
|
|
|
|
|
|
|
if (node.name() != "Route") {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode* ds_child = find_named_node (node, X_("Diskstream"));
|
|
|
|
|
|
|
|
DataType type = DataType::AUDIO;
|
|
|
|
node.get_property("default-type", type);
|
|
|
|
|
|
|
|
assert (type != DataType::NIL);
|
|
|
|
|
|
|
|
if (ds_child) {
|
|
|
|
|
|
|
|
boost::shared_ptr<Track> track;
|
|
|
|
|
|
|
|
if (type == DataType::AUDIO) {
|
2020-03-15 09:03:24 -04:00
|
|
|
track.reset (new AudioTrack (*this));
|
2017-08-17 10:33:38 -04:00
|
|
|
} else {
|
2020-03-15 09:03:24 -04:00
|
|
|
track.reset (new MidiTrack (*this));
|
2017-08-17 10:33:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (track->init()) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (track->set_state (node, version)) {
|
2017-04-20 17:47:39 -04:00
|
|
|
return ret;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
BOOST_MARK_TRACK (track);
|
|
|
|
ret = track;
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2010-04-21 16:42:22 -04:00
|
|
|
} else {
|
2016-05-16 07:30:28 -04:00
|
|
|
PresentationInfo::Flag flags = PresentationInfo::get_flags (node);
|
2014-06-11 11:14:18 -04:00
|
|
|
boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML"), flags));
|
2010-03-24 23:40:07 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (r->init () == 0 && r->set_state (node, version) == 0) {
|
|
|
|
BOOST_MARK_ROUTE (r);
|
|
|
|
ret = r;
|
|
|
|
}
|
2010-04-21 16:42:22 -04:00
|
|
|
}
|
2010-03-24 23:40:07 -04:00
|
|
|
|
2010-04-21 16:42:22 -04:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::shared_ptr<Route>
|
|
|
|
Session::XMLRouteFactory_2X (const XMLNode& node, int version)
|
|
|
|
{
|
|
|
|
boost::shared_ptr<Route> ret;
|
|
|
|
|
|
|
|
if (node.name() != "Route") {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
XMLProperty const * ds_prop = node.property (X_("diskstream-id"));
|
|
|
|
if (!ds_prop) {
|
|
|
|
ds_prop = node.property (X_("diskstream"));
|
|
|
|
}
|
|
|
|
|
|
|
|
DataType type = DataType::AUDIO;
|
2016-08-28 07:12:16 -04:00
|
|
|
node.get_property("default-type", type);
|
2010-04-21 16:42:22 -04:00
|
|
|
|
|
|
|
assert (type != DataType::NIL);
|
|
|
|
|
|
|
|
if (ds_prop) {
|
|
|
|
|
2019-12-16 18:34:26 -05:00
|
|
|
PBD::ID ds_id (ds_prop->value ());
|
|
|
|
std::string playlist_name = _diskstreams_2X[ds_id];
|
|
|
|
|
|
|
|
boost::shared_ptr<Playlist> pl = playlists()->by_name (playlist_name);
|
2010-04-21 16:42:22 -04:00
|
|
|
|
2019-12-16 18:34:26 -05:00
|
|
|
if (playlist_name.empty () || !pl) {
|
2020-04-16 18:02:38 -04:00
|
|
|
warning << string_compose (_("Could not find diskstream for diskstream-id: '%1', playlist: '%2'"), ds_prop->value (), playlist_name) << endmsg;
|
2019-12-16 18:34:26 -05:00
|
|
|
}
|
2010-03-24 23:40:07 -04:00
|
|
|
|
2011-02-22 16:15:42 -05:00
|
|
|
boost::shared_ptr<Track> track;
|
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (type == DataType::AUDIO) {
|
2020-03-15 09:03:24 -04:00
|
|
|
track.reset (new AudioTrack (*this));
|
2017-04-20 17:47:39 -04:00
|
|
|
} else {
|
2020-03-15 09:03:24 -04:00
|
|
|
track.reset (new MidiTrack (*this));
|
2017-04-20 17:47:39 -04:00
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (track->init()) {
|
|
|
|
return ret;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2019-12-17 16:54:55 -05:00
|
|
|
if (pl) {
|
|
|
|
track->use_playlist (DataType::AUDIO, pl);
|
|
|
|
}
|
2019-12-16 18:34:26 -05:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (track->set_state (node, version)) {
|
|
|
|
return ret;
|
|
|
|
}
|
2010-04-21 16:42:22 -04:00
|
|
|
|
2019-12-17 16:54:55 -05:00
|
|
|
if (pl) {
|
|
|
|
pl->set_orig_track_id (track->id());
|
|
|
|
playlists()->update_orig_2X (ds_id, track->id());
|
|
|
|
}
|
2019-12-16 18:34:26 -05:00
|
|
|
|
2016-05-07 13:32:31 -04:00
|
|
|
BOOST_MARK_TRACK (track);
|
2017-04-20 17:47:39 -04:00
|
|
|
ret = track;
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
} else {
|
2016-05-16 07:30:28 -04:00
|
|
|
PresentationInfo::Flag flags = PresentationInfo::get_flags (node);
|
2014-06-11 11:14:18 -04:00
|
|
|
boost::shared_ptr<Route> r (new Route (*this, X_("toBeResetFroXML"), flags));
|
2010-03-24 23:40:07 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (r->init () == 0 && r->set_state (node, version) == 0) {
|
|
|
|
BOOST_MARK_ROUTE (r);
|
|
|
|
ret = r;
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-12-26 11:15:11 -05:00
|
|
|
|
|
|
|
return ret;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
Session::load_regions (const XMLNode& node)
|
|
|
|
{
|
|
|
|
XMLNodeList nlist;
|
|
|
|
XMLNodeConstIterator niter;
|
|
|
|
boost::shared_ptr<Region> region;
|
|
|
|
|
|
|
|
nlist = node.children();
|
|
|
|
|
|
|
|
set_dirty();
|
|
|
|
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((region = XMLRegionFactory (**niter, false)) == 0) {
|
|
|
|
error << _("Session: cannot create Region from XML description.");
|
2016-05-04 23:09:37 -04:00
|
|
|
XMLProperty const * name = (**niter).property("name");
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (name) {
|
|
|
|
error << " " << string_compose (_("Can not load state for region '%1'"), name->value());
|
|
|
|
}
|
|
|
|
|
|
|
|
error << endmsg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-05-22 12:11:00 -04:00
|
|
|
int
|
|
|
|
Session::load_compounds (const XMLNode& node)
|
|
|
|
{
|
|
|
|
XMLNodeList calist = node.children();
|
|
|
|
XMLNodeConstIterator caiter;
|
2016-05-04 23:09:37 -04:00
|
|
|
XMLProperty const * caprop;
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-05-22 12:11:00 -04:00
|
|
|
for (caiter = calist.begin(); caiter != calist.end(); ++caiter) {
|
|
|
|
XMLNode* ca = *caiter;
|
|
|
|
ID orig_id;
|
|
|
|
ID copy_id;
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-05-22 12:11:00 -04:00
|
|
|
if ((caprop = ca->property (X_("original"))) == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
orig_id = caprop->value();
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-05-22 12:11:00 -04:00
|
|
|
if ((caprop = ca->property (X_("copy"))) == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
copy_id = caprop->value();
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-05-22 12:11:00 -04:00
|
|
|
boost::shared_ptr<Region> orig = RegionFactory::region_by_id (orig_id);
|
|
|
|
boost::shared_ptr<Region> copy = RegionFactory::region_by_id (copy_id);
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-05-22 12:11:00 -04:00
|
|
|
if (!orig || !copy) {
|
|
|
|
warning << string_compose (_("Regions in compound description not found (ID's %1 and %2): ignored"),
|
2011-06-01 12:50:12 -04:00
|
|
|
orig_id, copy_id)
|
2011-05-22 12:11:00 -04:00
|
|
|
<< endmsg;
|
|
|
|
continue;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-05-22 12:11:00 -04:00
|
|
|
RegionFactory::add_compound_association (orig, copy);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-05-26 08:24:04 -04:00
|
|
|
void
|
|
|
|
Session::load_nested_sources (const XMLNode& node)
|
|
|
|
{
|
|
|
|
XMLNodeList nlist;
|
|
|
|
XMLNodeConstIterator niter;
|
|
|
|
|
|
|
|
nlist = node.children();
|
|
|
|
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((*niter)->name() == "Source") {
|
2011-06-22 18:46:31 -04:00
|
|
|
|
2015-10-04 12:46:27 -04:00
|
|
|
/* it may already exist, so don't recreate it unnecessarily
|
2011-06-22 18:46:31 -04:00
|
|
|
*/
|
|
|
|
|
2016-05-04 23:09:37 -04:00
|
|
|
XMLProperty const * prop = (*niter)->property (X_("id"));
|
2011-06-22 18:46:31 -04:00
|
|
|
if (!prop) {
|
2013-01-09 09:33:10 -05:00
|
|
|
error << _("Nested source has no ID info in session file! (ignored)") << endmsg;
|
2011-06-22 18:46:31 -04:00
|
|
|
continue;
|
2011-06-01 12:50:12 -04:00
|
|
|
}
|
2011-06-22 18:46:31 -04:00
|
|
|
|
|
|
|
ID source_id (prop->value());
|
|
|
|
|
|
|
|
if (!source_by_id (source_id)) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
SourceFactory::create (*this, **niter, true);
|
|
|
|
}
|
|
|
|
catch (failed_constructor& err) {
|
|
|
|
error << string_compose (_("Cannot reconstruct nested source for region %1"), name()) << endmsg;
|
|
|
|
}
|
2011-05-26 08:24:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
boost::shared_ptr<Region>
|
|
|
|
Session::XMLRegionFactory (const XMLNode& node, bool full)
|
|
|
|
{
|
2016-05-04 23:09:37 -04:00
|
|
|
XMLProperty const * type = node.property("type");
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
try {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2011-05-26 08:24:04 -04:00
|
|
|
const XMLNodeList& nlist = node.children();
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-05-26 08:24:04 -04:00
|
|
|
for (XMLNodeConstIterator niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
XMLNode *child = (*niter);
|
|
|
|
if (child->name() == "NestedSource") {
|
|
|
|
load_nested_sources (*child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (!type || type->value() == "audio") {
|
|
|
|
return boost::shared_ptr<Region>(XMLAudioRegionFactory (node, full));
|
|
|
|
} else if (type->value() == "midi") {
|
|
|
|
return boost::shared_ptr<Region>(XMLMidiRegionFactory (node, full));
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
} catch (failed_constructor& err) {
|
|
|
|
return boost::shared_ptr<Region> ();
|
|
|
|
}
|
|
|
|
|
|
|
|
return boost::shared_ptr<Region> ();
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::shared_ptr<AudioRegion>
|
2009-07-21 11:55:17 -04:00
|
|
|
Session::XMLAudioRegionFactory (const XMLNode& node, bool /*full*/)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2016-05-04 23:09:37 -04:00
|
|
|
XMLProperty const * prop;
|
2008-06-02 17:41:35 -04:00
|
|
|
boost::shared_ptr<Source> source;
|
|
|
|
boost::shared_ptr<AudioSource> as;
|
|
|
|
SourceList sources;
|
|
|
|
SourceList master_sources;
|
|
|
|
uint32_t nchans = 1;
|
|
|
|
char buf[128];
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (node.name() != X_("Region")) {
|
|
|
|
return boost::shared_ptr<AudioRegion>();
|
|
|
|
}
|
|
|
|
|
2016-08-28 07:12:16 -04:00
|
|
|
node.get_property (X_("channels"), nchans);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if ((prop = node.property ("name")) == 0) {
|
|
|
|
cerr << "no name for this region\n";
|
|
|
|
abort ();
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if ((prop = node.property (X_("source-0"))) == 0) {
|
|
|
|
if ((prop = node.property ("source")) == 0) {
|
|
|
|
error << _("Session: XMLNode describing a AudioRegion is incomplete (no source)") << endmsg;
|
|
|
|
return boost::shared_ptr<AudioRegion>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PBD::ID s_id (prop->value());
|
|
|
|
|
|
|
|
if ((source = source_by_id (s_id)) == 0) {
|
|
|
|
error << string_compose(_("Session: XMLNode describing a AudioRegion references an unknown source id =%1"), s_id) << endmsg;
|
|
|
|
return boost::shared_ptr<AudioRegion>();
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
as = boost::dynamic_pointer_cast<AudioSource>(source);
|
|
|
|
if (!as) {
|
|
|
|
error << string_compose(_("Session: XMLNode describing a AudioRegion references a non-audio source id =%1"), s_id) << endmsg;
|
|
|
|
return boost::shared_ptr<AudioRegion>();
|
|
|
|
}
|
|
|
|
|
|
|
|
sources.push_back (as);
|
|
|
|
|
|
|
|
/* pickup other channels */
|
|
|
|
|
|
|
|
for (uint32_t n=1; n < nchans; ++n) {
|
|
|
|
snprintf (buf, sizeof(buf), X_("source-%d"), n);
|
|
|
|
if ((prop = node.property (buf)) != 0) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
PBD::ID id2 (prop->value());
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
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<AudioRegion>();
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
as = boost::dynamic_pointer_cast<AudioSource>(source);
|
|
|
|
if (!as) {
|
|
|
|
error << string_compose(_("Session: XMLNode describing a AudioRegion references a non-audio source id =%1"), id2) << endmsg;
|
|
|
|
return boost::shared_ptr<AudioRegion>();
|
|
|
|
}
|
|
|
|
sources.push_back (as);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-14 12:54:40 -04:00
|
|
|
for (uint32_t n = 0; n < nchans; ++n) {
|
2008-06-02 17:41:35 -04:00
|
|
|
snprintf (buf, sizeof(buf), X_("master-source-%d"), n);
|
|
|
|
if ((prop = node.property (buf)) != 0) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
PBD::ID id2 (prop->value());
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
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<AudioRegion>();
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
as = boost::dynamic_pointer_cast<AudioSource>(source);
|
|
|
|
if (!as) {
|
|
|
|
error << string_compose(_("Session: XMLNode describing a AudioRegion references a non-audio source id =%1"), id2) << endmsg;
|
|
|
|
return boost::shared_ptr<AudioRegion>();
|
|
|
|
}
|
|
|
|
master_sources.push_back (as);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
boost::shared_ptr<AudioRegion> region (boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (sources, node)));
|
|
|
|
|
|
|
|
/* a final detail: this is the one and only place that we know how long missing files are */
|
|
|
|
|
|
|
|
if (region->whole_file()) {
|
|
|
|
for (SourceList::iterator sx = sources.begin(); sx != sources.end(); ++sx) {
|
|
|
|
boost::shared_ptr<SilentFileSource> sfp = boost::dynamic_pointer_cast<SilentFileSource> (*sx);
|
|
|
|
if (sfp) {
|
|
|
|
sfp->set_length (region->length());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!master_sources.empty()) {
|
2009-06-14 12:54:40 -04:00
|
|
|
if (master_sources.size() != nchans) {
|
2008-06-02 17:41:35 -04:00
|
|
|
error << _("Session: XMLNode describing an AudioRegion is missing some master sources; ignored") << endmsg;
|
|
|
|
} else {
|
|
|
|
region->set_master_sources (master_sources);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return region;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
catch (failed_constructor& err) {
|
|
|
|
return boost::shared_ptr<AudioRegion>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::shared_ptr<MidiRegion>
|
2009-07-21 11:55:17 -04:00
|
|
|
Session::XMLMidiRegionFactory (const XMLNode& node, bool /*full*/)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2016-05-04 23:09:37 -04:00
|
|
|
XMLProperty const * prop;
|
2008-06-02 17:41:35 -04:00
|
|
|
boost::shared_ptr<Source> source;
|
|
|
|
boost::shared_ptr<MidiSource> ms;
|
|
|
|
SourceList sources;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (node.name() != X_("Region")) {
|
|
|
|
return boost::shared_ptr<MidiRegion>();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((prop = node.property ("name")) == 0) {
|
|
|
|
cerr << "no name for this region\n";
|
|
|
|
abort ();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((prop = node.property (X_("source-0"))) == 0) {
|
|
|
|
if ((prop = node.property ("source")) == 0) {
|
|
|
|
error << _("Session: XMLNode describing a MidiRegion is incomplete (no source)") << endmsg;
|
|
|
|
return boost::shared_ptr<MidiRegion>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PBD::ID s_id (prop->value());
|
|
|
|
|
|
|
|
if ((source = source_by_id (s_id)) == 0) {
|
|
|
|
error << string_compose(_("Session: XMLNode describing a MidiRegion references an unknown source id =%1"), s_id) << endmsg;
|
|
|
|
return boost::shared_ptr<MidiRegion>();
|
|
|
|
}
|
|
|
|
|
|
|
|
ms = boost::dynamic_pointer_cast<MidiSource>(source);
|
|
|
|
if (!ms) {
|
|
|
|
error << string_compose(_("Session: XMLNode describing a MidiRegion references a non-midi source id =%1"), s_id) << endmsg;
|
|
|
|
return boost::shared_ptr<MidiRegion>();
|
|
|
|
}
|
|
|
|
|
|
|
|
sources.push_back (ms);
|
|
|
|
|
|
|
|
try {
|
|
|
|
boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (sources, node)));
|
|
|
|
/* a final detail: this is the one and only place that we know how long missing files are */
|
|
|
|
|
|
|
|
if (region->whole_file()) {
|
|
|
|
for (SourceList::iterator sx = sources.begin(); sx != sources.end(); ++sx) {
|
|
|
|
boost::shared_ptr<SilentFileSource> sfp = boost::dynamic_pointer_cast<SilentFileSource> (*sx);
|
|
|
|
if (sfp) {
|
|
|
|
sfp->set_length (region->length());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return region;
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (failed_constructor& err) {
|
|
|
|
return boost::shared_ptr<MidiRegion>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode&
|
|
|
|
Session::get_sources_as_xml ()
|
|
|
|
|
|
|
|
{
|
|
|
|
XMLNode* node = new XMLNode (X_("Sources"));
|
2012-07-25 13:48:55 -04:00
|
|
|
Glib::Threads::Mutex::Lock lm (source_lock);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2020-02-24 22:15:30 -05:00
|
|
|
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
|
2008-06-02 17:41:35 -04:00
|
|
|
node->add_child_nocopy (i->second->get_state());
|
|
|
|
}
|
|
|
|
|
|
|
|
return *node;
|
|
|
|
}
|
|
|
|
|
2015-04-06 21:18:52 -04:00
|
|
|
void
|
|
|
|
Session::reset_write_sources (bool mark_write_complete, bool force)
|
|
|
|
{
|
2015-05-12 10:16:26 -04:00
|
|
|
boost::shared_ptr<RouteList> rl = routes.reader();
|
|
|
|
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
|
|
|
|
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
|
|
|
|
if (tr) {
|
2019-03-18 10:33:05 -04:00
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state | InCleanup);
|
2015-04-06 21:18:52 -04:00
|
|
|
tr->reset_write_sources(mark_write_complete, force);
|
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state & ~InCleanup);
|
2015-05-12 10:16:26 -04:00
|
|
|
}
|
|
|
|
}
|
2015-04-06 21:18:52 -04:00
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
int
|
|
|
|
Session::load_sources (const XMLNode& node)
|
|
|
|
{
|
|
|
|
XMLNodeList nlist;
|
|
|
|
XMLNodeConstIterator niter;
|
2017-02-22 04:25:58 -05:00
|
|
|
/* don't need this but it stops some
|
|
|
|
* versions of gcc complaining about
|
|
|
|
* discarded return values.
|
|
|
|
*/
|
|
|
|
boost::shared_ptr<Source> source;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
nlist = node.children();
|
|
|
|
|
|
|
|
set_dirty();
|
2017-02-22 04:38:17 -05:00
|
|
|
std::map<std::string, std::string> relocation;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
2016-10-12 20:07:08 -04:00
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
|
|
int old_mode = 0;
|
|
|
|
#endif
|
|
|
|
|
2017-02-22 04:38:17 -05:00
|
|
|
XMLNode srcnode (**niter);
|
|
|
|
bool try_replace_abspath = true;
|
|
|
|
|
2017-02-22 04:25:58 -05:00
|
|
|
retry:
|
2008-06-02 17:41:35 -04:00
|
|
|
try {
|
2016-10-12 20:07:08 -04:00
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
|
|
// do not show "insert media" popups (files embedded from removable media).
|
|
|
|
old_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
|
|
|
|
#endif
|
2017-02-22 04:38:17 -05:00
|
|
|
if ((source = XMLSourceFactory (srcnode)) == 0) {
|
2008-06-02 17:41:35 -04:00
|
|
|
error << _("Session: cannot create Source from XML description.") << endmsg;
|
|
|
|
}
|
2016-10-12 20:07:08 -04:00
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
|
|
SetErrorMode(old_mode);
|
|
|
|
#endif
|
2010-11-09 01:03:51 -05:00
|
|
|
|
2009-02-16 21:11:49 -05:00
|
|
|
} catch (MissingSource& err) {
|
2016-10-12 20:07:08 -04:00
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
|
|
SetErrorMode(old_mode);
|
|
|
|
#endif
|
2010-11-09 01:03:51 -05:00
|
|
|
|
2017-02-22 04:38:17 -05:00
|
|
|
/* 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)) {
|
2016-08-28 07:12:16 -04:00
|
|
|
srcnode.set_property ("origin", newpath);
|
2017-02-22 04:38:17 -05:00
|
|
|
try_replace_abspath = false;
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-22 04:25:58 -05:00
|
|
|
int user_choice;
|
2017-02-22 04:38:17 -05:00
|
|
|
_missing_file_replacement = "";
|
2010-11-09 01:03:51 -05:00
|
|
|
|
2014-09-24 19:03:59 -04:00
|
|
|
if (err.type == DataType::MIDI && Glib::path_is_absolute (err.path)) {
|
2017-02-22 04:38:17 -05:00
|
|
|
error << string_compose (_("An external MIDI file is missing. %1 cannot currently recover from missing external MIDI files"),
|
2017-02-22 04:25:58 -05:00
|
|
|
PROGRAM_NAME) << endmsg;
|
2014-09-24 19:03:59 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-02-22 04:25:58 -05:00
|
|
|
if (!no_questions_about_missing_files) {
|
2019-11-21 11:48:56 -05:00
|
|
|
user_choice = MissingFile (this, err.path, err.type).value_or (-1);
|
2014-09-24 19:03:59 -04:00
|
|
|
} else {
|
2017-02-22 04:25:58 -05:00
|
|
|
user_choice = -2;
|
|
|
|
}
|
2010-11-09 01:03:51 -05:00
|
|
|
|
2017-02-22 04:25:58 -05:00
|
|
|
switch (user_choice) {
|
|
|
|
case 0:
|
2017-02-22 04:38:17 -05:00
|
|
|
/* 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));
|
2016-08-28 07:12:16 -04:00
|
|
|
srcnode.set_property ("origin", newpath);
|
2017-02-22 04:38:17 -05:00
|
|
|
relocation[Glib::path_get_dirname (err.path)] = _missing_file_replacement;
|
|
|
|
_missing_file_replacement = "";
|
|
|
|
}
|
|
|
|
}
|
2017-02-22 04:25:58 -05:00
|
|
|
goto retry;
|
2010-11-09 01:03:51 -05:00
|
|
|
|
2014-09-15 14:09:01 -04:00
|
|
|
|
2017-02-22 04:25:58 -05:00
|
|
|
case 1:
|
|
|
|
/* user asked to quit the entire session load */
|
|
|
|
return -1;
|
2014-09-15 14:09:01 -04:00
|
|
|
|
2017-02-22 04:25:58 -05:00
|
|
|
case 2:
|
|
|
|
no_questions_about_missing_files = true;
|
|
|
|
goto retry;
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
no_questions_about_missing_files = true;
|
2019-09-18 11:27:09 -04:00
|
|
|
/* fallthrough */
|
2017-02-22 04:25:58 -05:00
|
|
|
|
|
|
|
case -1:
|
|
|
|
default:
|
|
|
|
switch (err.type) {
|
|
|
|
|
|
|
|
case DataType::AUDIO:
|
2017-09-18 12:39:17 -04:00
|
|
|
source = SourceFactory::createSilent (*this, **niter, max_samplecnt, _current_sample_rate);
|
2017-02-22 04:25:58 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
/* Note that we do not announce the source just yet - we need to reset its ID before we do that */
|
2020-02-25 23:35:07 -05:00
|
|
|
source = SourceFactory::createWritable (DataType::MIDI, *this, fullpath, _current_sample_rate, false, false);
|
2017-02-22 04:25:58 -05:00
|
|
|
/* reset ID to match the missing one */
|
|
|
|
source->set_id (**niter);
|
|
|
|
/* Now we can announce it */
|
|
|
|
SourceFactory::SourceCreated (source);
|
|
|
|
break;
|
2014-09-24 19:03:59 -04:00
|
|
|
}
|
2014-09-15 14:09:01 -04:00
|
|
|
break;
|
2017-02-22 04:25:58 -05:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::shared_ptr<Source>
|
|
|
|
Session::XMLSourceFactory (const XMLNode& node)
|
|
|
|
{
|
|
|
|
if (node.name() != "Source") {
|
|
|
|
return boost::shared_ptr<Source>();
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
/* note: do peak building in another thread when loading session state */
|
|
|
|
return SourceFactory::create (*this, node, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (failed_constructor& err) {
|
2014-07-09 14:13:31 -04:00
|
|
|
error << string_compose (_("Found a sound file that cannot be used by %1. Talk to the programmers."), PROGRAM_NAME) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
return boost::shared_ptr<Source>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2017-08-18 13:53:46 -04:00
|
|
|
Session::save_template (const string& template_name, const string& description, bool replace_existing)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2019-03-18 10:33:05 -04:00
|
|
|
if (cannot_save () || template_name.empty ()) {
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-05-08 11:41:00 -04:00
|
|
|
bool absolute_path = Glib::path_is_absolute (template_name);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2015-05-08 11:41:00 -04:00
|
|
|
/* directory to put the template in */
|
|
|
|
std::string template_dir_path;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2015-05-08 11:41:00 -04:00
|
|
|
if (!absolute_path) {
|
|
|
|
std::string user_template_dir(user_template_directory());
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2015-05-08 11:41:00 -04:00
|
|
|
if (g_mkdir_with_parents (user_template_dir.c_str(), 0755) != 0) {
|
|
|
|
error << string_compose(_("Could not create templates directory \"%1\" (%2)"),
|
|
|
|
user_template_dir, g_strerror (errno)) << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
template_dir_path = Glib::build_filename (user_template_dir, template_name);
|
|
|
|
} else {
|
|
|
|
template_dir_path = template_name;
|
|
|
|
}
|
2012-06-23 01:09:37 -04:00
|
|
|
|
2019-09-25 15:02:31 -04:00
|
|
|
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 -2;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2019-09-25 15:02:31 -04:00
|
|
|
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;
|
|
|
|
return -1;
|
2012-06-23 01:09:19 -04:00
|
|
|
}
|
2011-12-11 15:38:42 -05:00
|
|
|
|
|
|
|
/* file to write */
|
2015-05-08 11:41:00 -04:00
|
|
|
std::string template_file_path;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2019-09-25 15:02:31 -04:00
|
|
|
if (absolute_path) {
|
|
|
|
template_file_path = Glib::build_filename (template_dir_path, Glib::path_get_basename (template_dir_path) + template_suffix);
|
2015-05-08 11:41:00 -04:00
|
|
|
} else {
|
2019-09-25 15:02:31 -04:00
|
|
|
template_file_path = Glib::build_filename (template_dir_path, template_name + template_suffix);
|
2015-05-08 11:41:00 -04:00
|
|
|
}
|
|
|
|
|
2015-05-08 11:42:16 -04:00
|
|
|
SessionSaveUnderway (); /* EMIT SIGNAL */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-08 11:41:00 -04:00
|
|
|
XMLTree tree;
|
2017-08-18 13:53:46 -04:00
|
|
|
XMLNode* root;
|
2015-12-19 08:46:15 -05:00
|
|
|
{
|
|
|
|
PBD::Unwinder<std::string> uw (_template_state_dir, template_dir_path);
|
2017-08-19 13:45:08 -04:00
|
|
|
root = &get_template ();
|
2017-08-18 13:53:46 -04:00
|
|
|
}
|
|
|
|
|
2017-08-20 16:23:10 -04:00
|
|
|
root->remove_nodes_and_delete (X_("description"));
|
|
|
|
|
2017-08-18 13:53:46 -04:00
|
|
|
if (!description.empty()) {
|
2017-08-19 13:45:08 -04:00
|
|
|
XMLNode* desc = new XMLNode (X_("description"));
|
|
|
|
XMLNode* desc_cont = new XMLNode (X_("content"), description);
|
2017-08-18 13:53:46 -04:00
|
|
|
desc->add_child_nocopy (*desc_cont);
|
|
|
|
|
|
|
|
root->add_child_nocopy (*desc);
|
2015-12-19 08:46:15 -05:00
|
|
|
}
|
|
|
|
|
2017-08-18 13:53:46 -04:00
|
|
|
tree.set_root (root);
|
|
|
|
|
2012-06-23 01:09:37 -04:00
|
|
|
if (!tree.write (template_file_path)) {
|
2011-02-17 09:05:21 -05:00
|
|
|
error << _("template not saved") << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-05-08 12:07:33 -04:00
|
|
|
store_recent_templates (template_file_path);
|
|
|
|
|
2011-12-11 15:38:42 -05:00
|
|
|
return 0;
|
2008-09-10 11:03:30 -04:00
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
void
|
|
|
|
Session::refresh_disk_space ()
|
|
|
|
{
|
2016-07-05 09:22:22 -04:00
|
|
|
#if __APPLE__ || __FreeBSD__ || __NetBSD__ || (HAVE_SYS_VFS_H && HAVE_SYS_STATVFS_H)
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2012-07-25 13:48:55 -04:00
|
|
|
Glib::Threads::Mutex::Lock lm (space_lock);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
/* get freespace on every FS that is part of the session path */
|
|
|
|
|
|
|
|
_total_free_4k_blocks = 0;
|
2012-06-12 12:41:29 -04:00
|
|
|
_total_free_4k_blocks_uncertain = false;
|
|
|
|
|
|
|
|
for (vector<space_and_path>::iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
2016-07-05 09:22:22 -04:00
|
|
|
#if defined(__NetBSD__)
|
|
|
|
struct statvfs statfsbuf;
|
2012-06-12 12:41:29 -04:00
|
|
|
|
2016-07-05 09:22:22 -04:00
|
|
|
statvfs (i->path.c_str(), &statfsbuf);
|
|
|
|
#else
|
2012-06-12 12:41:29 -04:00
|
|
|
struct statfs statfsbuf;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2016-07-05 09:22:22 -04:00
|
|
|
statfs (i->path.c_str(), &statfsbuf);
|
|
|
|
#endif
|
2012-06-12 12:41:29 -04:00
|
|
|
double const scale = statfsbuf.f_bsize / 4096.0;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-12 12:41:29 -04:00
|
|
|
/* See if this filesystem is read-only */
|
|
|
|
struct statvfs statvfsbuf;
|
|
|
|
statvfs (i->path.c_str(), &statvfsbuf);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-12 12:41:29 -04:00
|
|
|
/* f_bavail can be 0 if it is undefined for whatever
|
|
|
|
filesystem we are looking at; Samba shares mounted
|
|
|
|
via GVFS are an example of this.
|
|
|
|
*/
|
|
|
|
if (statfsbuf.f_bavail == 0) {
|
|
|
|
/* block count unknown */
|
|
|
|
i->blocks = 0;
|
|
|
|
i->blocks_unknown = true;
|
|
|
|
} else if (statvfsbuf.f_flag & ST_RDONLY) {
|
|
|
|
/* read-only filesystem */
|
|
|
|
i->blocks = 0;
|
|
|
|
i->blocks_unknown = false;
|
|
|
|
} else {
|
|
|
|
/* read/write filesystem with known space */
|
|
|
|
i->blocks = (uint32_t) floor (statfsbuf.f_bavail * scale);
|
|
|
|
i->blocks_unknown = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_total_free_4k_blocks += i->blocks;
|
|
|
|
if (i->blocks_unknown) {
|
|
|
|
_total_free_4k_blocks_uncertain = true;
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2015-03-09 19:21:37 -04:00
|
|
|
#elif defined PLATFORM_WINDOWS
|
2013-08-04 12:01:37 -04:00
|
|
|
vector<string> scanned_volumes;
|
|
|
|
vector<string>::iterator j;
|
|
|
|
vector<space_and_path>::iterator i;
|
2017-04-20 17:47:39 -04:00
|
|
|
DWORD nSectorsPerCluster, nBytesPerSector,
|
|
|
|
nFreeClusters, nTotalClusters;
|
|
|
|
char disk_drive[4];
|
2013-08-04 12:01:37 -04:00
|
|
|
bool volume_found;
|
|
|
|
|
|
|
|
_total_free_4k_blocks = 0;
|
|
|
|
|
|
|
|
for (i = session_dirs.begin(); i != session_dirs.end(); i++) {
|
|
|
|
strncpy (disk_drive, (*i).path.c_str(), 3);
|
|
|
|
disk_drive[3] = 0;
|
|
|
|
strupr(disk_drive);
|
|
|
|
|
|
|
|
volume_found = false;
|
|
|
|
if (0 != (GetDiskFreeSpace(disk_drive, &nSectorsPerCluster, &nBytesPerSector, &nFreeClusters, &nTotalClusters)))
|
|
|
|
{
|
|
|
|
int64_t nBytesPerCluster = nBytesPerSector * nSectorsPerCluster;
|
|
|
|
int64_t nFreeBytes = nBytesPerCluster * (int64_t)nFreeClusters;
|
|
|
|
i->blocks = (uint32_t)(nFreeBytes / 4096);
|
|
|
|
|
|
|
|
for (j = scanned_volumes.begin(); j != scanned_volumes.end(); j++) {
|
|
|
|
if (0 == j->compare(disk_drive)) {
|
|
|
|
volume_found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!volume_found) {
|
|
|
|
scanned_volumes.push_back(disk_drive);
|
|
|
|
_total_free_4k_blocks += i->blocks;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 == _total_free_4k_blocks) {
|
|
|
|
strncpy (disk_drive, path().c_str(), 3);
|
|
|
|
disk_drive[3] = 0;
|
|
|
|
|
|
|
|
if (0 != (GetDiskFreeSpace(disk_drive, &nSectorsPerCluster, &nBytesPerSector, &nFreeClusters, &nTotalClusters)))
|
|
|
|
{
|
|
|
|
int64_t nBytesPerCluster = nBytesPerSector * nSectorsPerCluster;
|
|
|
|
int64_t nFreeBytes = nBytesPerCluster * (int64_t)nFreeClusters;
|
|
|
|
_total_free_4k_blocks = (uint32_t)(nFreeBytes / 4096);
|
|
|
|
}
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
string
|
2014-06-02 11:20:37 -04:00
|
|
|
Session::get_best_session_directory_for_new_audio ()
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
vector<space_and_path>::iterator i;
|
2012-06-23 01:06:54 -04:00
|
|
|
string result = _session_dir->root_path();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
/* handle common case without system calls */
|
|
|
|
|
|
|
|
if (session_dirs.size() == 1) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* OK, here's the algorithm we're following here:
|
2009-10-14 12:10:01 -04:00
|
|
|
|
|
|
|
We want to select which directory to use for
|
2008-06-02 17:41:35 -04:00
|
|
|
the next file source to be created. Ideally,
|
|
|
|
we'd like to use a round-robin process so as to
|
|
|
|
get maximum performance benefits from splitting
|
|
|
|
the files across multiple disks.
|
|
|
|
|
|
|
|
However, in situations without much diskspace, an
|
|
|
|
RR approach may end up filling up a filesystem
|
|
|
|
with new files while others still have space.
|
|
|
|
Its therefore important to pay some attention to
|
|
|
|
the freespace in the filesystem holding each
|
|
|
|
directory as well. However, if we did that by
|
|
|
|
itself, we'd keep creating new files in the file
|
|
|
|
system with the most space until it was as full
|
|
|
|
as all others, thus negating any performance
|
|
|
|
benefits of this RAID-1 like approach.
|
|
|
|
|
|
|
|
So, we use a user-configurable space threshold. If
|
|
|
|
there are at least 2 filesystems with more than this
|
2009-10-14 12:10:01 -04:00
|
|
|
much space available, we use RR selection between them.
|
2008-06-02 17:41:35 -04:00
|
|
|
If not, then we pick the filesystem with the most space.
|
|
|
|
|
|
|
|
This gets a good balance between the two
|
2009-10-14 12:10:01 -04:00
|
|
|
approaches.
|
2008-06-02 17:41:35 -04:00
|
|
|
*/
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
refresh_disk_space ();
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
int free_enough = 0;
|
|
|
|
|
|
|
|
for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
|
|
|
if ((*i).blocks * 4096 >= Config->get_disk_choice_space_threshold()) {
|
|
|
|
free_enough++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (free_enough >= 2) {
|
|
|
|
/* use RR selection process, ensuring that the one
|
|
|
|
picked works OK.
|
|
|
|
*/
|
|
|
|
|
|
|
|
i = last_rr_session_dir;
|
|
|
|
|
|
|
|
do {
|
|
|
|
if (++i == session_dirs.end()) {
|
|
|
|
i = session_dirs.begin();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((*i).blocks * 4096 >= Config->get_disk_choice_space_threshold()) {
|
2012-06-23 03:33:34 -04:00
|
|
|
SessionDirectory sdir(i->path);
|
|
|
|
if (sdir.create ()) {
|
2008-06-02 17:41:35 -04:00
|
|
|
result = (*i).path;
|
|
|
|
last_rr_session_dir = i;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (i != last_rr_session_dir);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* pick FS with the most freespace (and that
|
|
|
|
seems to actually work ...)
|
|
|
|
*/
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
vector<space_and_path> sorted;
|
|
|
|
space_and_path_ascending_cmp cmp;
|
|
|
|
|
|
|
|
sorted = session_dirs;
|
|
|
|
sort (sorted.begin(), sorted.end(), cmp);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
for (i = sorted.begin(); i != sorted.end(); ++i) {
|
2012-06-23 03:33:34 -04:00
|
|
|
SessionDirectory sdir(i->path);
|
|
|
|
if (sdir.create ()) {
|
2008-06-02 17:41:35 -04:00
|
|
|
result = (*i).path;
|
|
|
|
last_rr_session_dir = i;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
string
|
|
|
|
Session::automation_dir () const
|
|
|
|
{
|
2015-05-15 13:54:43 -04:00
|
|
|
return Glib::build_filename (_path, automation_dir_name);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
string
|
|
|
|
Session::analysis_dir () const
|
|
|
|
{
|
2015-05-15 13:54:43 -04:00
|
|
|
return Glib::build_filename (_path, analysis_dir_name);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2010-11-25 17:12:03 -05:00
|
|
|
string
|
|
|
|
Session::plugins_dir () const
|
|
|
|
{
|
2015-05-15 13:54:43 -04:00
|
|
|
return Glib::build_filename (_path, plugins_dir_name);
|
2010-11-25 17:12:03 -05:00
|
|
|
}
|
|
|
|
|
2012-01-27 20:45:15 -05:00
|
|
|
string
|
|
|
|
Session::externals_dir () const
|
|
|
|
{
|
2015-05-15 13:54:43 -04:00
|
|
|
return Glib::build_filename (_path, externals_dir_name);
|
2012-01-27 20:45:15 -05:00
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
int
|
|
|
|
Session::load_bundles (XMLNode const & node)
|
|
|
|
{
|
2009-10-14 12:10:01 -04:00
|
|
|
XMLNodeList nlist = node.children();
|
|
|
|
XMLNodeConstIterator niter;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
set_dirty();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((*niter)->name() == "InputBundle") {
|
2008-06-02 17:41:35 -04:00
|
|
|
add_bundle (boost::shared_ptr<UserBundle> (new UserBundle (**niter, true)));
|
2009-10-14 12:10:01 -04:00
|
|
|
} else if ((*niter)->name() == "OutputBundle") {
|
|
|
|
add_bundle (boost::shared_ptr<UserBundle> (new UserBundle (**niter, false)));
|
|
|
|
} else {
|
2013-01-09 09:33:10 -05:00
|
|
|
error << string_compose(_("Unknown node \"%1\" found in Bundles list from session file"), (*niter)->name()) << endmsg;
|
2009-10-14 12:10:01 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
return 0;
|
2009-10-14 12:10:01 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
int
|
2009-10-14 20:57:55 -04:00
|
|
|
Session::load_route_groups (const XMLNode& node, int version)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
XMLNodeList nlist = node.children();
|
|
|
|
XMLNodeConstIterator niter;
|
|
|
|
|
2009-06-21 15:59:56 -04:00
|
|
|
set_dirty ();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-10-14 20:57:55 -04:00
|
|
|
if (version >= 3000) {
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2009-10-14 20:57:55 -04:00
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((*niter)->name() == "RouteGroup") {
|
|
|
|
RouteGroup* rg = new RouteGroup (*this, "");
|
|
|
|
add_route_group (rg);
|
|
|
|
rg->set_state (**niter, version);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (version < 3000) {
|
|
|
|
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((*niter)->name() == "EditGroup" || (*niter)->name() == "MixGroup") {
|
|
|
|
RouteGroup* rg = new RouteGroup (*this, "");
|
|
|
|
add_route_group (rg);
|
|
|
|
rg->set_state (**niter, version);
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
2009-10-14 12:10:01 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
static bool
|
2013-09-05 05:07:57 -04:00
|
|
|
state_file_filter (const string &str, void* /*arg*/)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
return (str.length() > strlen(statefile_suffix) &&
|
|
|
|
str.find (statefile_suffix) == (str.length() - strlen (statefile_suffix)));
|
|
|
|
}
|
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
static string
|
|
|
|
remove_end(string state)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2014-06-16 06:39:45 -04:00
|
|
|
string statename(state);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
string::size_type start,end;
|
2010-08-17 09:10:42 -04:00
|
|
|
if ((start = statename.find_last_of (G_DIR_SEPARATOR)) != string::npos) {
|
2008-06-02 17:41:35 -04:00
|
|
|
statename = statename.substr (start+1);
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2015-01-05 11:48:42 -05:00
|
|
|
if ((end = statename.rfind(statefile_suffix)) == string::npos) {
|
2008-06-02 17:41:35 -04:00
|
|
|
end = statename.length();
|
|
|
|
}
|
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
return string(statename.substr (0, end));
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
vector<string>
|
2009-10-14 12:10:01 -04:00
|
|
|
Session::possible_states (string path)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2014-06-17 07:41:29 -04:00
|
|
|
vector<string> states;
|
|
|
|
find_files_matching_filter (states, path, state_file_filter, 0, false, false);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
transform(states.begin(), states.end(), states.begin(), remove_end);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
sort (states.begin(), states.end());
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return states;
|
|
|
|
}
|
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
vector<string>
|
2008-06-02 17:41:35 -04:00
|
|
|
Session::possible_states () const
|
|
|
|
{
|
|
|
|
return possible_states(_path);
|
|
|
|
}
|
|
|
|
|
2016-12-05 15:53:41 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2009-06-20 11:40:26 -04:00
|
|
|
void
|
2009-06-21 15:59:56 -04:00
|
|
|
Session::add_route_group (RouteGroup* g)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2009-06-21 15:59:56 -04:00
|
|
|
_route_groups.push_back (g);
|
|
|
|
route_group_added (g); /* EMIT SIGNAL */
|
2010-07-04 21:12:49 -04:00
|
|
|
|
2011-09-07 07:56:23 -04:00
|
|
|
g->RouteAdded.connect_same_thread (*this, boost::bind (&Session::route_added_to_route_group, this, _1, _2));
|
|
|
|
g->RouteRemoved.connect_same_thread (*this, boost::bind (&Session::route_removed_from_route_group, this, _1, _2));
|
2016-01-19 14:16:49 -05:00
|
|
|
g->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::route_group_property_changed, this, g));
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2009-06-20 11:40:26 -04:00
|
|
|
set_dirty ();
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2009-06-20 11:40:26 -04:00
|
|
|
void
|
2009-06-21 15:59:56 -04:00
|
|
|
Session::remove_route_group (RouteGroup& rg)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
list<RouteGroup*>::iterator i;
|
|
|
|
|
2009-06-21 15:59:56 -04:00
|
|
|
if ((i = find (_route_groups.begin(), _route_groups.end(), &rg)) != _route_groups.end()) {
|
|
|
|
_route_groups.erase (i);
|
2009-12-09 22:25:32 -05:00
|
|
|
delete &rg;
|
|
|
|
|
2009-06-21 15:59:56 -04:00
|
|
|
route_group_removed (); /* EMIT SIGNAL */
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2011-04-19 11:46:47 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2011-04-19 11:46:47 -04:00
|
|
|
/** Set a new order for our route groups, without adding or removing any.
|
|
|
|
* @param groups Route group list in the new order.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
Session::reorder_route_groups (list<RouteGroup*> groups)
|
|
|
|
{
|
|
|
|
_route_groups = groups;
|
|
|
|
|
|
|
|
route_groups_reordered (); /* EMIT SIGNAL */
|
|
|
|
set_dirty ();
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2011-04-19 11:46:47 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
RouteGroup *
|
2009-06-21 15:59:56 -04:00
|
|
|
Session::route_group_by_name (string name)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
list<RouteGroup *>::iterator i;
|
|
|
|
|
2009-06-21 15:59:56 -04:00
|
|
|
for (i = _route_groups.begin(); i != _route_groups.end(); ++i) {
|
2008-06-02 17:41:35 -04:00
|
|
|
if ((*i)->name() == name) {
|
|
|
|
return* i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-08-26 19:25:44 -04:00
|
|
|
RouteGroup&
|
|
|
|
Session::all_route_group() const
|
|
|
|
{
|
2017-04-20 17:47:39 -04:00
|
|
|
return *_all_route_group;
|
2010-08-26 19:25:44 -04:00
|
|
|
}
|
|
|
|
|
2010-08-25 21:44:11 -04:00
|
|
|
void
|
|
|
|
Session::add_commands (vector<Command*> const & cmds)
|
|
|
|
{
|
|
|
|
for (vector<Command*>::const_iterator i = cmds.begin(); i != cmds.end(); ++i) {
|
|
|
|
add_command (*i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-18 23:45:01 -04:00
|
|
|
void
|
|
|
|
Session::add_command (Command* const cmd)
|
|
|
|
{
|
|
|
|
assert (_current_trans);
|
|
|
|
DEBUG_UNDO_HISTORY (
|
|
|
|
string_compose ("Current Undo Transaction %1, adding command: %2",
|
|
|
|
_current_trans->name (),
|
|
|
|
cmd->name ()));
|
|
|
|
_current_trans->add_command (cmd);
|
|
|
|
}
|
2016-04-09 19:45:41 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
void
|
2011-01-19 12:38:46 -05:00
|
|
|
Session::begin_reversible_command (const string& name)
|
2011-01-19 12:38:56 -05:00
|
|
|
{
|
|
|
|
begin_reversible_command (g_quark_from_string (name.c_str ()));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Begin a reversible command using a GQuark to identify it.
|
|
|
|
* begin_reversible_command() and commit_reversible_command() calls may be nested,
|
|
|
|
* but there must be as many begin...()s as there are commit...()s.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
Session::begin_reversible_command (GQuark q)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2011-01-19 12:38:46 -05:00
|
|
|
/* If nested begin/commit pairs are used, we create just one UndoTransaction
|
|
|
|
to hold all the commands that are committed. This keeps the order of
|
|
|
|
commands correct in the history.
|
|
|
|
*/
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-01-19 12:38:46 -05:00
|
|
|
if (_current_trans == 0) {
|
2015-08-18 23:45:01 -04:00
|
|
|
DEBUG_UNDO_HISTORY (string_compose (
|
|
|
|
"Begin Reversible Command, new transaction: %1", g_quark_to_string (q)));
|
|
|
|
|
2011-01-19 12:38:46 -05:00
|
|
|
/* start a new transaction */
|
2011-01-19 12:38:56 -05:00
|
|
|
assert (_current_trans_quarks.empty ());
|
2011-01-19 12:38:46 -05:00
|
|
|
_current_trans = new UndoTransaction();
|
2011-01-19 12:38:56 -05:00
|
|
|
_current_trans->set_name (g_quark_to_string (q));
|
2015-08-18 23:45:01 -04:00
|
|
|
} else {
|
|
|
|
DEBUG_UNDO_HISTORY (
|
|
|
|
string_compose ("Begin Reversible Command, current transaction: %1",
|
|
|
|
_current_trans->name ()));
|
2009-05-04 21:53:30 -04:00
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-01-19 12:38:56 -05:00
|
|
|
_current_trans_quarks.push_front (q);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2015-02-05 07:32:57 -05:00
|
|
|
void
|
|
|
|
Session::abort_reversible_command ()
|
|
|
|
{
|
|
|
|
if (_current_trans != 0) {
|
2015-08-18 23:45:01 -04:00
|
|
|
DEBUG_UNDO_HISTORY (
|
|
|
|
string_compose ("Abort Reversible Command: %1", _current_trans->name ()));
|
2015-02-05 07:32:57 -05:00
|
|
|
_current_trans->clear();
|
|
|
|
delete _current_trans;
|
|
|
|
_current_trans = 0;
|
|
|
|
_current_trans_quarks.clear();
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-01-19 12:38:46 -05:00
|
|
|
Session::commit_reversible_command (Command *cmd)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2011-01-19 12:38:46 -05:00
|
|
|
assert (_current_trans);
|
2011-01-19 12:38:56 -05:00
|
|
|
assert (!_current_trans_quarks.empty ());
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
struct timeval now;
|
|
|
|
|
|
|
|
if (cmd) {
|
2015-08-18 23:45:01 -04:00
|
|
|
DEBUG_UNDO_HISTORY (
|
|
|
|
string_compose ("Current Undo Transaction %1, adding command: %2",
|
|
|
|
_current_trans->name (),
|
|
|
|
cmd->name ()));
|
2011-01-19 12:38:46 -05:00
|
|
|
_current_trans->add_command (cmd);
|
|
|
|
}
|
|
|
|
|
2015-08-18 23:45:01 -04:00
|
|
|
DEBUG_UNDO_HISTORY (
|
|
|
|
string_compose ("Commit Reversible Command, current transaction: %1",
|
|
|
|
_current_trans->name ()));
|
|
|
|
|
2011-01-19 12:38:56 -05:00
|
|
|
_current_trans_quarks.pop_front ();
|
2011-01-19 12:38:46 -05:00
|
|
|
|
2011-01-19 12:38:56 -05:00
|
|
|
if (!_current_trans_quarks.empty ()) {
|
2015-08-18 23:45:01 -04:00
|
|
|
DEBUG_UNDO_HISTORY (
|
|
|
|
string_compose ("Commit Reversible Command, transaction is not "
|
|
|
|
"top-level, current transaction: %1",
|
|
|
|
_current_trans->name ()));
|
2011-01-19 12:38:46 -05:00
|
|
|
/* the transaction we're committing is not the top-level one */
|
|
|
|
return;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2011-01-19 12:38:46 -05:00
|
|
|
if (_current_trans->empty()) {
|
|
|
|
/* no commands were added to the transaction, so just get rid of it */
|
2015-08-18 23:45:01 -04:00
|
|
|
DEBUG_UNDO_HISTORY (
|
|
|
|
string_compose ("Commit Reversible Command, No commands were "
|
|
|
|
"added to current transaction: %1",
|
|
|
|
_current_trans->name ()));
|
2011-01-19 12:38:46 -05:00
|
|
|
delete _current_trans;
|
|
|
|
_current_trans = 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-01-19 12:38:46 -05:00
|
|
|
gettimeofday (&now, 0);
|
|
|
|
_current_trans->set_timestamp (now);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2011-01-19 12:38:46 -05:00
|
|
|
_history.add (_current_trans);
|
|
|
|
_current_trans = 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
2013-09-05 05:07:57 -04:00
|
|
|
accept_all_audio_files (const string& path, void* /*arg*/)
|
2011-06-01 12:50:12 -04:00
|
|
|
{
|
2017-04-20 17:47:39 -04:00
|
|
|
if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
|
|
|
|
return false;
|
|
|
|
}
|
2011-03-01 16:54:54 -05:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
if (!AudioFileSource::safe_audio_file_extension (path)) {
|
|
|
|
return false;
|
|
|
|
}
|
2011-03-01 16:54:54 -05:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
return true;
|
2011-03-01 16:54:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
2013-09-05 05:07:57 -04:00
|
|
|
accept_all_midi_files (const string& path, void* /*arg*/)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2017-04-20 17:47:39 -04:00
|
|
|
if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
|
|
|
|
return false;
|
|
|
|
}
|
2010-07-16 10:55:11 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
return ( (path.length() > 4 && path.find (".mid") != (path.length() - 4))
|
|
|
|
|| (path.length() > 4 && path.find (".smf") != (path.length() - 4))
|
|
|
|
|| (path.length() > 5 && path.find (".midi") != (path.length() - 5)));
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
2013-09-05 05:07:57 -04:00
|
|
|
accept_all_state_files (const string& path, void* /*arg*/)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2015-01-05 11:48:42 -05:00
|
|
|
if (!Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
|
|
|
|
return false;
|
|
|
|
}
|
2011-03-01 16:54:54 -05:00
|
|
|
|
2015-01-05 11:48:42 -05:00
|
|
|
std::string const statefile_ext (statefile_suffix);
|
|
|
|
if (path.length() >= statefile_ext.length()) {
|
|
|
|
return (0 == path.compare (path.length() - statefile_ext.length(), statefile_ext.length(), statefile_ext));
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
int
|
2008-06-02 17:41:35 -04:00
|
|
|
Session::find_all_sources (string path, set<string>& result)
|
|
|
|
{
|
|
|
|
XMLTree tree;
|
|
|
|
XMLNode* node;
|
|
|
|
|
|
|
|
if (!tree.read (path)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((node = find_named_node (*tree.root(), "Sources")) == 0) {
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
|
|
|
XMLNodeList nlist;
|
|
|
|
XMLNodeConstIterator niter;
|
|
|
|
|
|
|
|
nlist = node->children();
|
|
|
|
|
|
|
|
set_dirty();
|
|
|
|
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2016-05-04 23:09:37 -04:00
|
|
|
XMLProperty const * prop;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-06-25 16:46:39 -04:00
|
|
|
if ((prop = (*niter)->property (X_("type"))) == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
DataType type (prop->value());
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if ((prop = (*niter)->property (X_("name"))) == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2010-08-17 09:10:42 -04:00
|
|
|
if (Glib::path_is_absolute (prop->value())) {
|
2008-06-02 17:41:35 -04:00
|
|
|
/* external file, ignore */
|
|
|
|
continue;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2010-09-14 11:45:21 -04:00
|
|
|
string found_path;
|
2009-06-25 16:46:39 -04:00
|
|
|
bool is_new;
|
|
|
|
uint16_t chan;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2010-11-09 01:03:51 -05:00
|
|
|
if (FileSource::find (*this, type, prop->value(), true, is_new, chan, found_path)) {
|
2009-06-25 16:46:39 -04:00
|
|
|
result.insert (found_path);
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_this_snapshot)
|
|
|
|
{
|
2014-06-16 06:39:45 -04:00
|
|
|
vector<string> state_files;
|
2008-06-02 17:41:35 -04:00
|
|
|
string ripped;
|
|
|
|
string this_snapshot_path;
|
|
|
|
|
|
|
|
result.clear ();
|
|
|
|
|
|
|
|
ripped = _path;
|
|
|
|
|
2010-08-17 09:10:42 -04:00
|
|
|
if (ripped[ripped.length()-1] == G_DIR_SEPARATOR) {
|
2008-06-02 17:41:35 -04:00
|
|
|
ripped = ripped.substr (0, ripped.length() - 1);
|
|
|
|
}
|
|
|
|
|
2014-06-17 07:41:29 -04:00
|
|
|
find_files_matching_filter (state_files, ripped, accept_all_state_files, (void *) 0, true, true);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
if (state_files.empty()) {
|
2008-06-02 17:41:35 -04:00
|
|
|
/* impossible! */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
this_snapshot_path = Glib::build_filename (_path, legalize_for_path (_current_snapshot_name));
|
2008-06-02 17:41:35 -04:00
|
|
|
this_snapshot_path += statefile_suffix;
|
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
for (vector<string>::iterator i = state_files.begin(); i != state_files.end(); ++i) {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
cerr << "Looking at snapshot " << (*i) << " ( with this = [" << this_snapshot_path << "])\n";
|
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
if (exclude_this_snapshot && *i == this_snapshot_path) {
|
2016-06-28 15:05:48 -04:00
|
|
|
cerr << "\texcluded\n";
|
2008-06-02 17:41:35 -04:00
|
|
|
continue;
|
2016-06-28 15:05:48 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
if (find_all_sources (*i, result) < 0) {
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct RegionCounter {
|
2017-04-20 17:47:39 -04:00
|
|
|
typedef std::map<PBD::ID,boost::shared_ptr<AudioSource> > AudioSourceList;
|
|
|
|
AudioSourceList::iterator iter;
|
|
|
|
boost::shared_ptr<Region> region;
|
|
|
|
uint32_t count;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
RegionCounter() : count (0) {}
|
2008-06-02 17:41:35 -04:00
|
|
|
};
|
|
|
|
|
2009-12-20 11:50:41 -05:00
|
|
|
int
|
|
|
|
Session::ask_about_playlist_deletion (boost::shared_ptr<Playlist> p)
|
|
|
|
{
|
2017-04-20 17:47:39 -04:00
|
|
|
boost::optional<int> r = AskAboutPlaylistDeletion (p);
|
2019-11-21 11:48:56 -05:00
|
|
|
return r.value_or (1);
|
2009-12-20 11:50:41 -05:00
|
|
|
}
|
|
|
|
|
2011-02-14 16:49:43 -05:00
|
|
|
void
|
|
|
|
Session::cleanup_regions ()
|
|
|
|
{
|
2014-10-10 08:12:48 -04:00
|
|
|
bool removed = false;
|
2011-02-14 16:49:43 -05:00
|
|
|
const RegionFactory::RegionMap& regions (RegionFactory::regions());
|
|
|
|
|
2015-09-23 16:23:43 -04:00
|
|
|
for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end();) {
|
2011-02-14 16:49:43 -05:00
|
|
|
|
2019-03-19 00:14:00 -04:00
|
|
|
uint32_t used = _playlists->region_use_count (i->second);
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2011-11-15 13:32:24 -05:00
|
|
|
if (used == 0 && !i->second->automatic ()) {
|
2015-09-23 16:23:43 -04:00
|
|
|
boost::weak_ptr<Region> w = i->second;
|
|
|
|
++i;
|
2014-10-10 08:12:48 -04:00
|
|
|
removed = true;
|
2015-09-23 16:23:43 -04:00
|
|
|
RegionFactory::map_remove (w);
|
|
|
|
} else {
|
|
|
|
++i;
|
2011-02-14 16:49:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-10 08:12:48 -04:00
|
|
|
if (removed) {
|
|
|
|
// re-check to remove parent references of compound regions
|
2015-09-23 16:23:43 -04:00
|
|
|
for (RegionFactory::RegionMap::const_iterator i = regions.begin(); i != regions.end();) {
|
2014-10-10 08:12:48 -04:00
|
|
|
if (!(i->second->whole_file() && i->second->max_source_level() > 0)) {
|
2015-09-23 16:23:43 -04:00
|
|
|
++i;
|
2014-10-10 08:12:48 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
assert(boost::dynamic_pointer_cast<PlaylistSource>(i->second->source (0)) != 0);
|
2019-03-19 00:14:00 -04:00
|
|
|
if (0 == _playlists->region_use_count (i->second)) {
|
2015-09-23 16:23:43 -04:00
|
|
|
boost::weak_ptr<Region> w = i->second;
|
|
|
|
++i;
|
|
|
|
RegionFactory::map_remove (w);
|
|
|
|
} else {
|
|
|
|
++i;
|
2014-10-10 08:12:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-14 16:49:43 -05:00
|
|
|
/* dump the history list */
|
|
|
|
_history.clear ();
|
|
|
|
|
|
|
|
save_state ("");
|
|
|
|
}
|
|
|
|
|
2015-09-15 08:41:27 -04:00
|
|
|
bool
|
|
|
|
Session::can_cleanup_peakfiles () const
|
|
|
|
{
|
|
|
|
if (deletion_in_progress()) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-18 10:33:05 -04:00
|
|
|
if (!_writable || cannot_save ()) {
|
2015-09-15 08:41:27 -04:00
|
|
|
warning << _("Cannot cleanup peak-files for read-only session.") << endmsg;
|
|
|
|
return false;
|
|
|
|
}
|
2017-04-20 17:47:39 -04:00
|
|
|
if (record_status() == Recording) {
|
2015-09-15 08:41:27 -04:00
|
|
|
error << _("Cannot cleanup peak-files while recording") << endmsg;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
Session::cleanup_peakfiles ()
|
|
|
|
{
|
|
|
|
Glib::Threads::Mutex::Lock lm (peak_cleanup_lock, Glib::Threads::TRY_LOCK);
|
|
|
|
if (!lm.locked()) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert (can_cleanup_peakfiles ());
|
|
|
|
assert (!peaks_cleanup_in_progres());
|
|
|
|
|
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state | PeakCleanup);
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
int
|
2009-10-30 11:30:22 -04:00
|
|
|
Session::cleanup_sources (CleanupReport& rep)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
// FIXME: needs adaptation to midi
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2020-02-25 09:32:17 -05:00
|
|
|
SourceList dead_sources;
|
2011-03-01 16:54:54 -05:00
|
|
|
string audio_path;
|
|
|
|
string midi_path;
|
2014-06-16 06:39:45 -04:00
|
|
|
vector<string> candidates;
|
2008-06-02 17:41:35 -04:00
|
|
|
vector<string> unused;
|
2016-06-28 15:05:48 -04:00
|
|
|
set<string> sources_used_by_all_snapshots;
|
2008-06-02 17:41:35 -04:00
|
|
|
string spath;
|
|
|
|
int ret = -1;
|
2013-07-11 14:57:16 -04:00
|
|
|
string tmppath1;
|
|
|
|
string tmppath2;
|
2014-06-28 15:57:04 -04:00
|
|
|
Searchpath asp;
|
|
|
|
Searchpath msp;
|
2016-06-28 15:05:48 -04:00
|
|
|
set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2019-03-18 10:33:05 -04:00
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state | InCleanup);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2020-02-25 09:32:17 -05:00
|
|
|
Glib::Threads::Mutex::Lock ls (source_lock, Glib::Threads::NOT_LOCK);
|
|
|
|
|
2015-10-04 14:17:52 -04:00
|
|
|
/* 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 ();
|
|
|
|
|
2011-03-01 16:54:54 -05:00
|
|
|
/* consider deleting all unused playlists */
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2019-03-19 00:14:00 -04:00
|
|
|
if (_playlists->maybe_delete_unused (boost::bind (Session::ask_about_playlist_deletion, _1))) {
|
2009-12-03 16:52:10 -05:00
|
|
|
ret = 0;
|
|
|
|
goto out;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-12-20 11:50:41 -05:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
/* sync the "all regions" property of each playlist with its current state */
|
2011-03-01 16:54:54 -05:00
|
|
|
|
2019-03-19 00:14:00 -04:00
|
|
|
_playlists->sync_all_regions_with_regions ();
|
2011-03-01 16:54:54 -05:00
|
|
|
|
|
|
|
/* find all un-used sources */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
rep.paths.clear ();
|
|
|
|
rep.space = 0;
|
|
|
|
|
2020-02-25 09:32:17 -05:00
|
|
|
ls.acquire ();
|
2008-06-02 17:41:35 -04:00
|
|
|
for (SourceMap::iterator i = sources.begin(); i != sources.end(); ) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
SourceMap::iterator tmp;
|
|
|
|
|
|
|
|
tmp = i;
|
|
|
|
++tmp;
|
|
|
|
|
|
|
|
/* do not bother with files that are zero size, otherwise we remove the current "nascent"
|
2017-04-20 17:47:39 -04:00
|
|
|
* capture files.
|
|
|
|
*/
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2018-11-15 10:33:54 -05:00
|
|
|
if (!i->second->used() && (i->second->length(i->second->natural_position()) > 0)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
dead_sources.push_back (i->second);
|
2009-10-14 12:10:01 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
i = tmp;
|
|
|
|
}
|
2020-02-25 09:32:17 -05:00
|
|
|
ls.release ();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2011-03-01 16:54:54 -05:00
|
|
|
/* build a list of all the possible audio directories for the session */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2014-06-28 15:57:04 -04:00
|
|
|
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
2008-06-02 17:41:35 -04:00
|
|
|
SessionDirectory sdir ((*i).path);
|
2014-06-28 15:57:04 -04:00
|
|
|
asp += sdir.sound_path();
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2014-06-28 15:57:04 -04:00
|
|
|
audio_path += asp.to_string();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
|
2011-03-01 16:54:54 -05:00
|
|
|
/* build a list of all the possible midi directories for the session */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2014-06-28 15:57:04 -04:00
|
|
|
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
2011-03-01 16:54:54 -05:00
|
|
|
SessionDirectory sdir ((*i).path);
|
2014-06-28 15:57:04 -04:00
|
|
|
msp += sdir.midi_path();
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2014-06-28 15:57:04 -04:00
|
|
|
midi_path += msp.to_string();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2014-06-17 07:41:29 -04:00
|
|
|
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);
|
2011-03-01 16:54:54 -05:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
/* add sources from all other snapshots as "used", but don't use this
|
2017-04-20 17:47:39 -04:00
|
|
|
snapshot because the state file on disk still references sources we
|
|
|
|
may have already dropped.
|
|
|
|
*/
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2019-03-19 00:14:00 -04:00
|
|
|
_playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
/* add our current source list
|
2017-04-20 17:47:39 -04:00
|
|
|
*/
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2020-02-25 09:32:17 -05:00
|
|
|
ls.acquire ();
|
2011-03-01 16:54:54 -05:00
|
|
|
for (SourceMap::iterator i = sources.begin(); i != sources.end(); ) {
|
2009-02-16 21:11:49 -05:00
|
|
|
boost::shared_ptr<FileSource> fs;
|
2017-04-20 17:47:39 -04:00
|
|
|
SourceMap::iterator tmp = i;
|
|
|
|
++tmp;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
if ((fs = boost::dynamic_pointer_cast<FileSource> (i->second)) == 0) {
|
|
|
|
/* not a file */
|
|
|
|
i = tmp;
|
|
|
|
continue;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
/* 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.
|
|
|
|
*/
|
2015-10-04 14:17:52 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
fs->close ();
|
2015-10-04 14:17:52 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
if (!fs->is_stub()) {
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
/* Note that we're checking a list of all
|
|
|
|
* sources across all snapshots with the list
|
|
|
|
* of sources used by this snapshot.
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
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";
|
2017-04-20 17:47:39 -04:00
|
|
|
/* this source is NOT in use by this snapshot */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
/* remove all related regions from RegionFactory master list */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
RegionFactory::remove_regions_using_source (i->second);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
/* 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.
|
|
|
|
*/
|
2015-07-02 14:15:11 -04:00
|
|
|
|
2020-02-25 09:32:17 -05:00
|
|
|
dead_sources.push_back (i->second);
|
2016-06-28 15:05:48 -04:00
|
|
|
sources.erase (i);
|
2014-02-07 17:38:42 -05:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
}
|
2011-03-01 16:54:54 -05:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
i = tmp;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2020-02-25 09:32:17 -05:00
|
|
|
ls.release ();
|
|
|
|
|
|
|
|
for (SourceList::iterator i = dead_sources.begin(); i != dead_sources.end(); ++i) {
|
|
|
|
/* The following triggers Region::source_deleted (), which
|
|
|
|
* causes regions to drop the given source */
|
|
|
|
(*i)->drop_references ();
|
|
|
|
SourceRemoved (*i); /* EMIT SIGNAL */
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
/* now check each candidate source to see if it exists in the list of
|
2017-04-20 17:47:39 -04:00
|
|
|
* sources_used_by_all_snapshots. If it doesn't, put it into "unused".
|
|
|
|
*/
|
2016-06-28 15:05:48 -04:00
|
|
|
|
|
|
|
cerr << "Candidates: " << candidates.size() << endl;
|
|
|
|
cerr << "Used by others: " << sources_used_by_all_snapshots.size() << endl;
|
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
for (vector<string>::iterator x = candidates.begin(); x != candidates.end(); ++x) {
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
bool used = false;
|
2014-06-16 06:39:45 -04:00
|
|
|
spath = *x;
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
for (set<string>::iterator i = sources_used_by_all_snapshots.begin(); i != sources_used_by_all_snapshots.end(); ++i) {
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
tmppath1 = canonical_path (spath);
|
|
|
|
tmppath2 = canonical_path ((*i));
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
cerr << "\t => " << tmppath2 << endl;
|
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
if (tmppath1 == tmppath2) {
|
|
|
|
used = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2014-06-16 06:39:45 -04:00
|
|
|
if (!used) {
|
|
|
|
unused.push_back (spath);
|
|
|
|
}
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-06-28 15:05:48 -04:00
|
|
|
cerr << "Actually unused: " << unused.size() << endl;
|
|
|
|
|
|
|
|
if (unused.empty()) {
|
|
|
|
/* Nothing to do */
|
|
|
|
ret = 0;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2011-03-01 16:54:54 -05:00
|
|
|
/* now try to move all unused files into the "dead" directory(ies) */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
for (vector<string>::iterator x = unused.begin(); x != unused.end(); ++x) {
|
2015-10-04 09:18:54 -04:00
|
|
|
GStatBuf statbuf;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
string newpath;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
/* don't move the file across filesystems, just
|
2017-04-20 17:47:39 -04:00
|
|
|
* stick it in the `dead_dir_name' directory
|
|
|
|
* on whichever filesystem it was already on.
|
|
|
|
*/
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if ((*x).find ("/sounds/") != string::npos) {
|
|
|
|
|
|
|
|
/* old school, go up 1 level */
|
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
newpath = Glib::path_get_dirname (*x); // "sounds"
|
2008-06-02 17:41:35 -04:00
|
|
|
newpath = Glib::path_get_dirname (newpath); // "session-name"
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* new school, go up 4 levels */
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2011-03-01 16:54:54 -05:00
|
|
|
newpath = Glib::path_get_dirname (*x); // "audiofiles" or "midifiles"
|
2008-06-02 17:41:35 -04:00
|
|
|
newpath = Glib::path_get_dirname (newpath); // "session-name"
|
|
|
|
newpath = Glib::path_get_dirname (newpath); // "interchange"
|
|
|
|
newpath = Glib::path_get_dirname (newpath); // "session-dir"
|
|
|
|
}
|
|
|
|
|
2011-03-01 16:54:54 -05:00
|
|
|
newpath = Glib::build_filename (newpath, dead_dir_name);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (g_mkdir_with_parents (newpath.c_str(), 0755) < 0) {
|
2011-03-01 16:54:54 -05:00
|
|
|
error << string_compose(_("Session: cannot create dead file folder \"%1\" (%2)"), newpath, strerror (errno)) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-08-17 09:10:42 -04:00
|
|
|
newpath = Glib::build_filename (newpath, Glib::path_get_basename ((*x)));
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2010-08-17 09:10:42 -04:00
|
|
|
if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
/* the new path already exists, try versioning */
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
char buf[PATH_MAX+1];
|
|
|
|
int version = 1;
|
|
|
|
string newpath_v;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
|
|
|
|
newpath_v = buf;
|
|
|
|
|
2011-03-01 16:54:54 -05:00
|
|
|
while (Glib::file_test (newpath_v.c_str(), Glib::FILE_TEST_EXISTS) && version < 999) {
|
2008-06-02 17:41:35 -04:00
|
|
|
snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
|
|
|
|
newpath_v = buf;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (version == 999) {
|
|
|
|
error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
|
2017-04-20 17:47:39 -04:00
|
|
|
newpath)
|
|
|
|
<< endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
} else {
|
|
|
|
newpath = newpath_v;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2016-09-14 07:48:33 -04:00
|
|
|
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),
|
2017-04-20 17:47:39 -04:00
|
|
|
newpath, g_strerror (errno)) << endmsg;
|
2016-09-14 07:48:33 -04:00
|
|
|
continue;
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
/* see if there an easy to find peakfile for this file, and remove it. */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-09-14 07:48:33 -04:00
|
|
|
string base = Glib::path_get_basename (*x);
|
|
|
|
base += "%A"; /* this is what we add for the channel suffix of all native files,
|
2017-04-20 17:47:39 -04:00
|
|
|
* or for the first channel of embedded files. it will miss
|
|
|
|
* some peakfiles for other channels
|
|
|
|
*/
|
2016-09-14 07:48:33 -04:00
|
|
|
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,
|
2017-04-20 17:47:39 -04:00
|
|
|
g_strerror (errno)) << endmsg;
|
2016-09-14 07:48:33 -04:00
|
|
|
/* try to back out */
|
|
|
|
::g_rename (newpath.c_str (), _path.c_str ());
|
|
|
|
goto out;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2016-09-14 04:57:11 -04:00
|
|
|
}
|
2016-09-14 07:48:33 -04:00
|
|
|
|
|
|
|
rep.paths.push_back (*x);
|
|
|
|
rep.space += statbuf.st_size;
|
2015-10-04 09:18:54 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2020-02-25 09:32:17 -05:00
|
|
|
/* drop last Source references */
|
|
|
|
dead_sources.clear ();
|
|
|
|
|
|
|
|
/* dump the history list, remove references */
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
_history.clear ();
|
|
|
|
|
|
|
|
/* save state so we don't end up a session file
|
2017-04-20 17:47:39 -04:00
|
|
|
* referring to non-existent sources.
|
|
|
|
*/
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
save_state ("");
|
2011-03-01 16:54:54 -05:00
|
|
|
ret = 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
out:
|
2019-03-18 10:33:05 -04:00
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state & ~InCleanup);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2009-10-30 11:30:22 -04:00
|
|
|
Session::cleanup_trash_sources (CleanupReport& rep)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
// FIXME: needs adaptation for MIDI
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
vector<space_and_path>::iterator i;
|
2011-03-01 16:54:54 -05:00
|
|
|
string dead_dir;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2011-03-10 22:40:42 -05:00
|
|
|
dead_dir = Glib::build_filename ((*i).path, dead_dir_name);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-04-20 17:47:39 -04:00
|
|
|
clear_directory (dead_dir, &rep.space, &rep.paths);
|
2010-07-16 10:55:11 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2010-07-16 10:55:11 -04:00
|
|
|
return 0;
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
void
|
|
|
|
Session::set_dirty ()
|
|
|
|
{
|
2017-03-11 20:36:33 -05:00
|
|
|
/* return early if there's nothing to do */
|
|
|
|
if (dirty ()) {
|
|
|
|
return;
|
|
|
|
}
|
2014-09-17 12:28:09 -04:00
|
|
|
|
2017-03-11 20:36:33 -05:00
|
|
|
/* never mark session dirty during loading */
|
2019-03-18 10:33:05 -04:00
|
|
|
if (loading () || deletion_in_progress ()) {
|
2014-09-17 12:28:09 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state | Dirty);
|
2017-03-11 20:36:33 -05:00
|
|
|
DirtyChanged(); /* EMIT SIGNAL */
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Session::set_clean ()
|
|
|
|
{
|
|
|
|
bool was_dirty = dirty();
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
_state_of_the_state = Clean;
|
|
|
|
|
|
|
|
if (was_dirty) {
|
|
|
|
DirtyChanged(); /* EMIT SIGNAL */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-18 10:33:05 -04:00
|
|
|
void
|
|
|
|
Session::unset_dirty (bool emit_dirty_changed)
|
|
|
|
{
|
|
|
|
bool was_dirty = dirty();
|
|
|
|
|
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state & (~Dirty));
|
|
|
|
|
|
|
|
if (was_dirty && emit_dirty_changed) {
|
|
|
|
DirtyChanged (); /* EMIT SIGNAL */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
void
|
|
|
|
Session::set_deletion_in_progress ()
|
|
|
|
{
|
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state | Deletion);
|
The great audio processing overhaul.
The vast majority of Route signal processing is now simply in the list of
processors. There are definitely regressions here, but there's also
a lot of things fixed. It's far too much work to let diverge anymore
regardless, so here it is.
The basic model is: A route has a fixed set of input channels (matching
its JACK input ports and diskstream). The first processor takes this
as input. The next processor is configured using the first processor's
output as input, and is allowed to choose whatever output it wants
given that input... and so on, and so on. Finally, the last processor's
requested output is used to set up the panner and create whatever Jack
ports are needed to output the data.
All 'special' internal processors (meter, fader, amp, insert, send) are
currently transparent: they read any input, and return the same set
of channels back (unmodified, except for amp).
User visible changes:
* LV2 Instrument support (tracks with both MIDI and audio channels)
* MIDI in/out plugin support
* Generic plugin replication (for MIDI plugins, MIDI/audio plugins)
* Movable meter point
Known Bugs:
* Things seem to get weird on loaded sessions
* Output delivery is sketchy
* 2.0 session loading was probably already broken...
but it's definitely broken now :)
Please test this and file bugs if you have any time...
git-svn-id: svn://localhost/ardour2/branches/3.0@5055 d708f5d6-7413-0410-9779-e7cbd77b26cf
2009-05-07 02:30:50 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
The great audio processing overhaul.
The vast majority of Route signal processing is now simply in the list of
processors. There are definitely regressions here, but there's also
a lot of things fixed. It's far too much work to let diverge anymore
regardless, so here it is.
The basic model is: A route has a fixed set of input channels (matching
its JACK input ports and diskstream). The first processor takes this
as input. The next processor is configured using the first processor's
output as input, and is allowed to choose whatever output it wants
given that input... and so on, and so on. Finally, the last processor's
requested output is used to set up the panner and create whatever Jack
ports are needed to output the data.
All 'special' internal processors (meter, fader, amp, insert, send) are
currently transparent: they read any input, and return the same set
of channels back (unmodified, except for amp).
User visible changes:
* LV2 Instrument support (tracks with both MIDI and audio channels)
* MIDI in/out plugin support
* Generic plugin replication (for MIDI plugins, MIDI/audio plugins)
* Movable meter point
Known Bugs:
* Things seem to get weird on loaded sessions
* Output delivery is sketchy
* 2.0 session loading was probably already broken...
but it's definitely broken now :)
Please test this and file bugs if you have any time...
git-svn-id: svn://localhost/ardour2/branches/3.0@5055 d708f5d6-7413-0410-9779-e7cbd77b26cf
2009-05-07 02:30:50 -04:00
|
|
|
void
|
|
|
|
Session::clear_deletion_in_progress ()
|
|
|
|
{
|
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state & (~Deletion));
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Session::add_controllable (boost::shared_ptr<Controllable> c)
|
|
|
|
{
|
|
|
|
/* this adds a controllable to the list managed by the Session.
|
|
|
|
this is a subset of those managed by the Controllable class
|
|
|
|
itself, and represents the only ones whose state will be saved
|
|
|
|
as part of the session.
|
|
|
|
*/
|
|
|
|
|
2012-07-25 13:48:55 -04:00
|
|
|
Glib::Threads::Mutex::Lock lm (controllables_lock);
|
2008-06-02 17:41:35 -04:00
|
|
|
controllables.insert (c);
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
boost::shared_ptr<Controllable>
|
|
|
|
Session::controllable_by_id (const PBD::ID& id)
|
|
|
|
{
|
2012-07-25 13:48:55 -04:00
|
|
|
Glib::Threads::Mutex::Lock lm (controllables_lock);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
for (Controllables::iterator i = controllables.begin(); i != controllables.end(); ++i) {
|
|
|
|
if ((*i)->id() == id) {
|
|
|
|
return *i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return boost::shared_ptr<Controllable>();
|
|
|
|
}
|
|
|
|
|
2017-05-05 07:31:21 -04:00
|
|
|
boost::shared_ptr<AutomationControl>
|
|
|
|
Session::automation_control_by_id (const PBD::ID& id)
|
|
|
|
{
|
|
|
|
return boost::dynamic_pointer_cast<AutomationControl> (controllable_by_id (id));
|
|
|
|
}
|
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
void
|
2008-09-17 04:44:51 -04:00
|
|
|
Session::add_instant_xml (XMLNode& node, bool write_to_config)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2009-10-13 16:43:28 -04:00
|
|
|
if (_writable) {
|
|
|
|
Stateful::add_instant_xml (node, _path);
|
|
|
|
}
|
|
|
|
|
2008-09-17 04:44:51 -04:00
|
|
|
if (write_to_config) {
|
|
|
|
Config->add_instant_xml (node);
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode*
|
|
|
|
Session::instant_xml (const string& node_name)
|
|
|
|
{
|
2016-10-30 17:25:18 -04:00
|
|
|
#ifdef MIXBUS // "Safe Mode" (shift + click open) -> also ignore instant.xml
|
|
|
|
if (get_disable_all_loaded_plugins ()) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
#endif
|
2008-06-02 17:41:35 -04:00
|
|
|
return Stateful::instant_xml (node_name, _path);
|
|
|
|
}
|
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
int
|
2008-06-02 17:41:35 -04:00
|
|
|
Session::save_history (string snapshot_name)
|
|
|
|
{
|
|
|
|
XMLTree tree;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2009-10-13 16:43:28 -04:00
|
|
|
if (!_writable) {
|
|
|
|
return 0;
|
2009-10-14 12:10:01 -04:00
|
|
|
}
|
2009-10-13 16:43:28 -04:00
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
if (snapshot_name.empty()) {
|
2008-06-02 17:41:35 -04:00
|
|
|
snapshot_name = _current_snapshot_name;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
|
|
|
const string history_filename = legalize_for_path (snapshot_name) + history_suffix;
|
2008-06-02 17:41:35 -04:00
|
|
|
const string backup_filename = history_filename + backup_suffix;
|
2012-06-23 01:08:44 -04:00
|
|
|
const std::string xml_path(Glib::build_filename (_session_dir->root_path(), history_filename));
|
|
|
|
const std::string backup_path(Glib::build_filename (_session_dir->root_path(), backup_filename));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:09:00 -04:00
|
|
|
if (Glib::file_test (xml_path, Glib::FILE_TEST_EXISTS)) {
|
2012-06-23 01:08:44 -04:00
|
|
|
if (::g_rename (xml_path.c_str(), backup_path.c_str()) != 0) {
|
2008-06-02 17:41:35 -04:00
|
|
|
error << _("could not backup old history file, current history not saved") << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2020-02-25 09:24:01 -05:00
|
|
|
if (!Config->get_save_history() || Config->get_saved_history_depth() < 0 ||
|
|
|
|
(_history.undo_depth() == 0 && _history.redo_depth() == 0)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
tree.set_root (&_history.get_state (Config->get_saved_history_depth()));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:08:44 -04:00
|
|
|
if (!tree.write (xml_path))
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2012-06-23 01:08:44 -04:00
|
|
|
error << string_compose (_("history could not be saved to %1"), xml_path) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:08:44 -04:00
|
|
|
if (g_remove (xml_path.c_str()) != 0) {
|
|
|
|
error << string_compose(_("Could not remove history file at path \"%1\" (%2)"),
|
|
|
|
xml_path, g_strerror (errno)) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2012-06-23 01:08:44 -04:00
|
|
|
if (::g_rename (backup_path.c_str(), xml_path.c_str()) != 0) {
|
2008-06-02 17:41:35 -04:00
|
|
|
error << string_compose (_("could not restore history file from backup %1 (%2)"),
|
2012-06-23 01:08:44 -04:00
|
|
|
backup_path, g_strerror (errno)) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
Session::restore_history (string snapshot_name)
|
|
|
|
{
|
|
|
|
XMLTree tree;
|
|
|
|
|
|
|
|
if (snapshot_name.empty()) {
|
|
|
|
snapshot_name = _current_snapshot_name;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2012-06-23 01:09:26 -04:00
|
|
|
const std::string xml_filename = legalize_for_path (snapshot_name) + history_suffix;
|
|
|
|
const std::string xml_path(Glib::build_filename (_session_dir->root_path(), xml_filename));
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2012-06-23 01:09:26 -04:00
|
|
|
info << "Loading history from " << xml_path << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-06-23 01:09:26 -04:00
|
|
|
if (!Glib::file_test (xml_path, Glib::FILE_TEST_EXISTS)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
info << string_compose (_("%1: no history file \"%2\" for this session."),
|
2012-06-23 01:09:26 -04:00
|
|
|
_name, xml_path) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-06-23 01:09:26 -04:00
|
|
|
if (!tree.read (xml_path)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
error << string_compose (_("Could not understand session history file \"%1\""),
|
2012-06-23 01:09:26 -04:00
|
|
|
xml_path) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// replace history
|
|
|
|
_history.clear();
|
|
|
|
|
2016-10-20 16:34:06 -04:00
|
|
|
for (XMLNodeConstIterator it = tree.root()->children().begin(); it != tree.root()->children().end(); ++it) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
|
|
|
XMLNode *t = *it;
|
|
|
|
|
2017-04-19 08:21:03 -04:00
|
|
|
std::string name;
|
|
|
|
int64_t tv_sec;
|
|
|
|
int64_t tv_usec;
|
|
|
|
|
|
|
|
if (!t->get_property ("name", name) || !t->get_property ("tv-sec", tv_sec) ||
|
|
|
|
!t->get_property ("tv-usec", tv_usec)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-01-27 09:45:12 -05:00
|
|
|
UndoTransaction* ut = new UndoTransaction ();
|
2017-04-19 08:21:03 -04:00
|
|
|
ut->set_name (name);
|
|
|
|
|
|
|
|
struct timeval tv;
|
|
|
|
tv.tv_sec = tv_sec;
|
|
|
|
tv.tv_usec = tv_usec;
|
2009-10-14 12:10:01 -04:00
|
|
|
ut->set_timestamp(tv);
|
|
|
|
|
|
|
|
for (XMLNodeConstIterator child_it = t->children().begin();
|
2008-10-05 19:14:48 -04:00
|
|
|
child_it != t->children().end(); child_it++)
|
2009-10-14 12:10:01 -04:00
|
|
|
{
|
|
|
|
XMLNode *n = *child_it;
|
|
|
|
Command *c;
|
|
|
|
|
|
|
|
if (n->name() == "MementoCommand" ||
|
2008-10-05 19:14:48 -04:00
|
|
|
n->name() == "MementoUndoCommand" ||
|
|
|
|
n->name() == "MementoRedoCommand") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
if ((c = memento_command_factory(n))) {
|
|
|
|
ut->add_command(c);
|
|
|
|
}
|
|
|
|
|
2010-12-09 16:34:46 -05:00
|
|
|
} else if (n->name() == "NoteDiffCommand") {
|
|
|
|
PBD::ID id (n->property("midi-source")->value());
|
2009-10-14 12:10:01 -04:00
|
|
|
boost::shared_ptr<MidiSource> midi_source =
|
|
|
|
boost::dynamic_pointer_cast<MidiSource, Source>(source_by_id(id));
|
2009-10-19 22:26:25 -04:00
|
|
|
if (midi_source) {
|
2010-12-09 16:34:46 -05:00
|
|
|
ut->add_command (new MidiModel::NoteDiffCommand(midi_source->model(), *n));
|
2009-10-14 12:10:01 -04:00
|
|
|
} else {
|
2010-12-09 16:34:46 -05:00
|
|
|
error << _("Failed to downcast MidiSource for NoteDiffCommand") << endmsg;
|
2009-10-14 12:10:01 -04:00
|
|
|
}
|
|
|
|
|
2010-12-28 13:19:40 -05:00
|
|
|
} else if (n->name() == "SysExDiffCommand") {
|
|
|
|
|
|
|
|
PBD::ID id (n->property("midi-source")->value());
|
|
|
|
boost::shared_ptr<MidiSource> midi_source =
|
|
|
|
boost::dynamic_pointer_cast<MidiSource, Source>(source_by_id(id));
|
|
|
|
if (midi_source) {
|
|
|
|
ut->add_command (new MidiModel::SysExDiffCommand (midi_source->model(), *n));
|
|
|
|
} else {
|
|
|
|
error << _("Failed to downcast MidiSource for SysExDiffCommand") << endmsg;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2010-12-28 13:19:40 -05:00
|
|
|
} else if (n->name() == "PatchChangeDiffCommand") {
|
|
|
|
|
|
|
|
PBD::ID id (n->property("midi-source")->value());
|
|
|
|
boost::shared_ptr<MidiSource> midi_source =
|
|
|
|
boost::dynamic_pointer_cast<MidiSource, Source>(source_by_id(id));
|
|
|
|
if (midi_source) {
|
|
|
|
ut->add_command (new MidiModel::PatchChangeDiffCommand (midi_source->model(), *n));
|
|
|
|
} else {
|
|
|
|
error << _("Failed to downcast MidiSource for PatchChangeDiffCommand") << endmsg;
|
|
|
|
}
|
|
|
|
|
2010-02-09 17:28:46 -05:00
|
|
|
} else if (n->name() == "StatefulDiffCommand") {
|
|
|
|
if ((c = stateful_diff_command_factory (n))) {
|
|
|
|
ut->add_command (c);
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
} else {
|
|
|
|
error << string_compose(_("Couldn't figure out how to make a Command out of a %1 XMLNode."), n->name()) << endmsg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_history.add (ut);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2009-05-15 21:22:43 -04:00
|
|
|
Session::config_changed (std::string p, bool ours)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2009-05-15 21:22:43 -04:00
|
|
|
if (ours) {
|
|
|
|
set_dirty ();
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2017-11-23 03:53:50 -05:00
|
|
|
if (p == "auto-loop") {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2017-01-16 13:42:04 -05:00
|
|
|
} else if (p == "session-monitoring") {
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "auto-input") {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (Config->get_monitoring_model() == HardwareMonitoring && transport_rolling()) {
|
|
|
|
/* auto-input only makes a difference if we're rolling */
|
2017-01-16 13:42:04 -05:00
|
|
|
set_track_monitor_input_status (!config.get_auto_input());
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "punch-in") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2020-02-26 20:36:16 -05:00
|
|
|
if (!punch_is_possible ()) {
|
|
|
|
if (config.get_punch_in ()) {
|
|
|
|
/* force off */
|
|
|
|
config.set_punch_in (false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2020-02-26 20:36:16 -05:00
|
|
|
Location* location;
|
2010-08-09 12:40:31 -04:00
|
|
|
if ((location = _locations->auto_punch_location()) != 0) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2009-05-13 20:13:27 -04:00
|
|
|
if (config.get_punch_in ()) {
|
2017-09-29 20:45:13 -04:00
|
|
|
auto_punch_start_changed (location);
|
2008-06-02 17:41:35 -04:00
|
|
|
} else {
|
2017-09-29 20:45:13 -04:00
|
|
|
clear_events (SessionEvent::PunchIn);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "punch-out") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2020-02-26 20:36:16 -05:00
|
|
|
if (!punch_is_possible ()) {
|
|
|
|
if (config.get_punch_out ()) {
|
|
|
|
/* force off */
|
|
|
|
config.set_punch_out (false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2020-02-26 20:36:16 -05:00
|
|
|
Location* location;
|
2010-08-09 12:40:31 -04:00
|
|
|
if ((location = _locations->auto_punch_location()) != 0) {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2009-05-13 20:13:27 -04:00
|
|
|
if (config.get_punch_out()) {
|
2017-09-29 20:45:13 -04:00
|
|
|
auto_punch_end_changed (location);
|
2008-06-02 17:41:35 -04:00
|
|
|
} else {
|
2009-12-03 21:15:12 -05:00
|
|
|
clear_events (SessionEvent::PunchOut);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "edit-mode") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2019-03-19 00:14:00 -04:00
|
|
|
Glib::Threads::Mutex::Lock lm (_playlists->lock);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2019-03-19 00:14:00 -04:00
|
|
|
for (SessionPlaylists::List::iterator i = _playlists->playlists.begin(); i != _playlists->playlists.end(); ++i) {
|
2008-06-02 17:41:35 -04:00
|
|
|
(*i)->set_edit_mode (Config->get_edit_mode ());
|
|
|
|
}
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "use-video-sync") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-05-15 21:53:43 -04:00
|
|
|
waiting_for_sync_offset = config.get_use_video_sync();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "mmc-control") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
//poke_midi_thread ();
|
|
|
|
|
2011-08-13 12:27:29 -04:00
|
|
|
} else if (p == "mmc-device-id" || p == "mmc-receive-id" || p == "mmc-receive-device-id") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-05 13:22:34 -04:00
|
|
|
_mmc->set_receive_device_id (Config->get_mmc_receive_device_id());
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2011-08-13 12:27:29 -04:00
|
|
|
} else if (p == "mmc-send-id" || p == "mmc-send-device-id") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-09-05 13:22:34 -04:00
|
|
|
_mmc->set_send_device_id (Config->get_mmc_send_device_id());
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "midi-control") {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
//poke_midi_thread ();
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "raid-path") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-05-13 20:13:27 -04:00
|
|
|
setup_raid_path (config.get_raid_path());
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-10-26 10:38:58 -04:00
|
|
|
} else if (p == "timecode-format") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
sync_time_vars ();
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "video-pullup") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
sync_time_vars ();
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "seamless-loop") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (play_loop && transport_rolling()) {
|
|
|
|
// to reset diskstreams etc
|
|
|
|
request_play_loop (true);
|
|
|
|
}
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "click-sound") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
setup_click_sounds (1);
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "click-emphasis-sound") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
setup_click_sounds (-1);
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "clicking") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (Config->get_clicking()) {
|
|
|
|
if (_click_io && click_data) { // don't require emphasis data
|
|
|
|
_clicking = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_clicking = false;
|
|
|
|
}
|
|
|
|
|
2017-04-01 17:52:43 -04:00
|
|
|
} else if (p == "click-record-only") {
|
|
|
|
|
|
|
|
_click_rec_only = Config->get_click_record_only();
|
|
|
|
|
2012-01-27 17:47:16 -05:00
|
|
|
} else if (p == "click-gain") {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2012-01-27 17:47:16 -05:00
|
|
|
if (_click_gain) {
|
2016-01-13 16:32:24 -05:00
|
|
|
_click_gain->gain_control()->set_value (Config->get_click_gain(), Controllable::NoGroup);
|
2012-01-27 17:47:16 -05:00
|
|
|
}
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "send-mtc") {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2011-01-03 18:55:00 -05:00
|
|
|
if (Config->get_send_mtc ()) {
|
2010-07-06 20:40:58 -04:00
|
|
|
/* mark us ready to send */
|
|
|
|
next_quarter_frame_to_send = 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "send-mmc") {
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2013-09-05 13:22:34 -04:00
|
|
|
_mmc->enable_send (Config->get_send_mmc ());
|
2017-09-30 10:48:30 -04:00
|
|
|
if (Config->get_send_mmc ()) {
|
|
|
|
/* re-initialize MMC */
|
|
|
|
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
|
|
|
|
send_immediate_mmc (MIDI::MachineControlCommand (Timecode::Time ()));
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "jack-time-master") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
engine().reset_timebase ();
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "native-file-header-format") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (!first_file_header_format_reset) {
|
|
|
|
reset_native_file_format ();
|
|
|
|
}
|
|
|
|
|
|
|
|
first_file_header_format_reset = false;
|
|
|
|
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "native-file-data-format") {
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (!first_file_data_format_reset) {
|
|
|
|
reset_native_file_format ();
|
|
|
|
}
|
|
|
|
|
|
|
|
first_file_data_format_reset = false;
|
|
|
|
|
2009-11-09 15:05:18 -05:00
|
|
|
} else if (p == "external-sync") {
|
2018-10-05 12:35:10 -04:00
|
|
|
request_sync_source (TransportMasterManager::instance().current());
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "denormal-model") {
|
2008-06-02 17:41:35 -04:00
|
|
|
setup_fpu ();
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "history-depth") {
|
2008-06-02 17:41:35 -04:00
|
|
|
set_history_depth (Config->get_history_depth());
|
2012-06-28 18:27:37 -04:00
|
|
|
} else if (p == "remote-model") {
|
2012-07-19 18:35:43 -04:00
|
|
|
/* XXX DO SOMETHING HERE TO TELL THE GUI THAT WE NEED
|
|
|
|
TO SET REMOTE ID'S
|
|
|
|
*/
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "initial-program-change") {
|
2008-10-09 10:15:45 -04:00
|
|
|
|
2013-09-05 13:22:34 -04:00
|
|
|
if (_mmc->output_port() && Config->get_initial_program_change() >= 0) {
|
2008-10-09 10:15:45 -04:00
|
|
|
MIDI::byte buf[2];
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-10-09 10:15:45 -04:00
|
|
|
buf[0] = MIDI::program; // channel zero by default
|
|
|
|
buf[1] = (Config->get_initial_program_change() & 0x7f);
|
|
|
|
|
2013-09-05 13:22:34 -04:00
|
|
|
_mmc->output_port()->midimsg (buf, sizeof (buf), 0);
|
2008-12-12 09:43:24 -05:00
|
|
|
}
|
2009-05-15 21:22:43 -04:00
|
|
|
} else if (p == "solo-mute-override") {
|
2009-06-09 16:21:19 -04:00
|
|
|
// catch_up_on_solo_mute_override ();
|
2020-01-11 11:48:42 -05:00
|
|
|
} else if (p == "listen-position" || p == "pfl-position" || p == "afl-position") {
|
2009-07-01 09:36:50 -04:00
|
|
|
listen_position_changed ();
|
|
|
|
} else if (p == "solo-control-is-listen-control") {
|
|
|
|
solo_control_mode_changed ();
|
2014-11-17 21:49:20 -05:00
|
|
|
} else if (p == "solo-mute-gain") {
|
2016-03-11 10:34:51 -05:00
|
|
|
_solo_cut_control->Changed (true, Controllable::NoGroup);
|
2010-12-01 15:49:22 -05:00
|
|
|
} else if (p == "timecode-offset" || p == "timecode-offset-negative") {
|
|
|
|
last_timecode_valid = false;
|
2011-05-27 20:59:53 -04:00
|
|
|
} else if (p == "playback-buffer-seconds") {
|
2017-09-18 12:39:17 -04:00
|
|
|
AudioSource::allocate_working_buffers (sample_rate());
|
2012-10-25 15:46:23 -04:00
|
|
|
} else if (p == "ltc-sink-port") {
|
|
|
|
reconnect_ltc_output ();
|
2012-11-13 15:29:28 -05:00
|
|
|
} else if (p == "timecode-generator-offset") {
|
|
|
|
ltc_tx_parse_offset();
|
2015-01-16 17:50:10 -05:00
|
|
|
} else if (p == "auto-return-target-list") {
|
2019-09-18 12:08:32 -04:00
|
|
|
if (!loading()) {
|
|
|
|
follow_playhead_priority ();
|
|
|
|
}
|
2018-12-11 12:22:28 -05:00
|
|
|
} else if (p == "use-monitor-bus") {
|
2019-03-01 10:45:42 -05:00
|
|
|
/* NB. This is always called when constructing a session,
|
|
|
|
* after restoring session state (if any),
|
|
|
|
* via post_engine_init() -> Config->map_parameters()
|
|
|
|
*/
|
|
|
|
bool want_ms = Config->get_use_monitor_bus();
|
|
|
|
bool have_ms = _monitor_out ? true : false;
|
|
|
|
if (loading ()) {
|
|
|
|
/* When loading an existing session, the config "use-monitor-bus"
|
|
|
|
* is ignored. Instead the sesion-state (xml) will have added the
|
|
|
|
* "monitor-route" and restored its state (and connections)
|
|
|
|
* if the session has a monitor-section.
|
|
|
|
* Update the config to reflect this.
|
|
|
|
*/
|
|
|
|
if (want_ms != have_ms) {
|
|
|
|
Config->set_use_monitor_bus (have_ms);
|
|
|
|
}
|
|
|
|
MonitorBusAddedOrRemoved (); /* EMIT SIGNAL */
|
|
|
|
} else {
|
|
|
|
/* Otherwise, Config::set_use_monitor_bus() does
|
|
|
|
* control the the presence of the monitor-section
|
|
|
|
* (new sessions, user initiated change)
|
|
|
|
*/
|
|
|
|
if (want_ms && !have_ms) {
|
|
|
|
add_monitor_section ();
|
|
|
|
} else if (!want_ms && have_ms) {
|
|
|
|
remove_monitor_section ();
|
|
|
|
}
|
2018-12-11 12:22:28 -05:00
|
|
|
}
|
2019-11-23 02:09:46 -05:00
|
|
|
} else if (p == "loop-fade-choice") {
|
|
|
|
last_loopend = 0; /* force locate to refill buffers with new loop boundary data */
|
|
|
|
auto_loop_changed (_locations->auto_loop_location());
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
set_dirty ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Session::set_history_depth (uint32_t d)
|
|
|
|
{
|
|
|
|
_history.set_depth (d);
|
|
|
|
}
|
2010-04-21 16:42:22 -04:00
|
|
|
|
2010-07-08 18:55:20 -04:00
|
|
|
/** Connect things to the MMC object */
|
2010-06-29 09:47:53 -04:00
|
|
|
void
|
|
|
|
Session::setup_midi_machine_control ()
|
|
|
|
{
|
2013-09-10 15:41:19 -04:00
|
|
|
_mmc = new MIDI::MachineControl;
|
2015-12-07 12:02:42 -05:00
|
|
|
|
|
|
|
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);
|
2013-09-10 15:41:19 -04:00
|
|
|
|
2013-09-05 13:22:34 -04:00
|
|
|
_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));
|
|
|
|
_mmc->Stop.connect_same_thread (*this, boost::bind (&Session::mmc_stop, this, _1));
|
|
|
|
_mmc->FastForward.connect_same_thread (*this, boost::bind (&Session::mmc_fast_forward, this, _1));
|
|
|
|
_mmc->Rewind.connect_same_thread (*this, boost::bind (&Session::mmc_rewind, this, _1));
|
|
|
|
_mmc->Pause.connect_same_thread (*this, boost::bind (&Session::mmc_pause, this, _1));
|
|
|
|
_mmc->RecordPause.connect_same_thread (*this, boost::bind (&Session::mmc_record_pause, this, _1));
|
|
|
|
_mmc->RecordStrobe.connect_same_thread (*this, boost::bind (&Session::mmc_record_strobe, this, _1));
|
|
|
|
_mmc->RecordExit.connect_same_thread (*this, boost::bind (&Session::mmc_record_exit, this, _1));
|
|
|
|
_mmc->Locate.connect_same_thread (*this, boost::bind (&Session::mmc_locate, this, _1, _2));
|
|
|
|
_mmc->Step.connect_same_thread (*this, boost::bind (&Session::mmc_step, this, _1, _2));
|
|
|
|
_mmc->Shuttle.connect_same_thread (*this, boost::bind (&Session::mmc_shuttle, this, _1, _2, _3));
|
|
|
|
_mmc->TrackRecordStatusChange.connect_same_thread (*this, boost::bind (&Session::mmc_record_enable, this, _1, _2, _3));
|
2010-06-29 09:47:53 -04:00
|
|
|
|
|
|
|
/* also handle MIDI SPP because its so common */
|
|
|
|
|
2013-09-05 13:22:34 -04:00
|
|
|
_mmc->SPPStart.connect_same_thread (*this, boost::bind (&Session::spp_start, this));
|
|
|
|
_mmc->SPPContinue.connect_same_thread (*this, boost::bind (&Session::spp_continue, this));
|
|
|
|
_mmc->SPPStop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this));
|
2010-06-29 09:47:53 -04:00
|
|
|
}
|
2011-02-11 11:14:54 -05:00
|
|
|
|
|
|
|
boost::shared_ptr<Controllable>
|
|
|
|
Session::solo_cut_control() const
|
|
|
|
{
|
2017-04-20 17:47:39 -04:00
|
|
|
/* the solo cut control is a bit of an anomaly, at least as of Febrary 2011. There are no other
|
|
|
|
* controls in Ardour that currently get presented to the user in the GUI that require
|
|
|
|
* access as a Controllable and are also NOT owned by some SessionObject (e.g. Route, or MonitorProcessor).
|
|
|
|
*
|
|
|
|
* its actually an RCConfiguration parameter, so we use a ProxyControllable to wrap
|
|
|
|
* it up as a Controllable. Changes to the Controllable will just map back to the RCConfiguration
|
|
|
|
* parameter.
|
|
|
|
*/
|
|
|
|
return _solo_cut_control;
|
2011-02-11 11:14:54 -05:00
|
|
|
}
|
2011-07-14 13:41:06 -04:00
|
|
|
|
2015-11-19 21:10:57 -05:00
|
|
|
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");
|
|
|
|
|
2018-11-29 08:25:52 -05:00
|
|
|
XMLNode last_used_snapshot ("LastUsedSnapshot");
|
|
|
|
last_used_snapshot.set_property ("name", n);
|
|
|
|
add_instant_xml (last_used_snapshot, false);
|
2015-11-19 21:10:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Session::set_snapshot_name (const std::string & n)
|
|
|
|
{
|
|
|
|
_current_snapshot_name = n;
|
|
|
|
save_snapshot_name (n);
|
|
|
|
}
|
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
int
|
2015-05-07 12:06:49 -04:00
|
|
|
Session::rename (const std::string& new_name)
|
2011-07-14 13:41:06 -04:00
|
|
|
{
|
|
|
|
string legal_name = legalize_for_path (new_name);
|
2015-01-12 12:57:05 -05:00
|
|
|
string new_path;
|
2011-07-14 13:41:06 -04:00
|
|
|
string oldstr;
|
|
|
|
string newstr;
|
|
|
|
bool first = true;
|
|
|
|
|
2012-06-23 01:06:54 -04:00
|
|
|
string const old_sources_root = _session_dir->sources_root();
|
2011-10-24 15:47:06 -04:00
|
|
|
|
2019-03-18 10:33:05 -04:00
|
|
|
if (!_writable || cannot_save ()) {
|
2015-04-05 10:02:07 -04:00
|
|
|
error << _("Cannot rename read-only session.") << endmsg;
|
|
|
|
return 0; // don't show "messed up" warning
|
|
|
|
}
|
2017-04-20 17:47:39 -04:00
|
|
|
if (record_status() == Recording) {
|
2015-04-05 10:02:07 -04:00
|
|
|
error << _("Cannot rename session while recording") << endmsg;
|
|
|
|
return 0; // don't show "messed up" warning
|
|
|
|
}
|
|
|
|
|
|
|
|
StateProtector stp (this);
|
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
/* Rename:
|
|
|
|
|
|
|
|
* session directory
|
|
|
|
* interchange subdirectory
|
|
|
|
* session file
|
|
|
|
* session history
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
* Backup files are left unchanged and not renamed.
|
|
|
|
*/
|
|
|
|
|
2015-06-02 12:08:59 -04:00
|
|
|
/* Windows requires that we close all files before attempting the
|
2015-06-02 12:27:39 -04:00
|
|
|
* rename. This works on other platforms, but isn't necessary there.
|
|
|
|
* Leave it in place for all platforms though, since it may help
|
|
|
|
* catch issues that could arise if the way Source files work ever
|
|
|
|
* change (since most developers are not using Windows).
|
2015-06-02 12:08:59 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
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) {
|
|
|
|
fs->close ();
|
|
|
|
}
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
/* pass one: not 100% safe check that the new directory names don't
|
|
|
|
* already exist ...
|
|
|
|
*/
|
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
oldstr = (*i).path;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
/* 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 ...
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
|
|
|
|
oldstr = oldstr.substr (0, oldstr.length() - 1);
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
string base = Glib::path_get_dirname (oldstr);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
newstr = Glib::build_filename (base, legal_name);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
cerr << "Looking for " << newstr << endl;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
if (Glib::file_test (newstr, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
cerr << " exists\n";
|
|
|
|
return -1;
|
2011-07-14 13:41:06 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Session dirs */
|
2015-01-12 12:57:05 -05:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
first = true;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
for (vector<space_and_path>::iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
2015-04-20 15:10:41 -04:00
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
vector<string> v;
|
|
|
|
|
|
|
|
oldstr = (*i).path;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
/* 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 ...
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
|
|
|
|
oldstr = oldstr.substr (0, oldstr.length() - 1);
|
|
|
|
}
|
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
string base = Glib::path_get_dirname (oldstr);
|
|
|
|
newstr = Glib::build_filename (base, legal_name);
|
2011-07-14 13:41:06 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
cerr << "for " << oldstr << " new dir = " << newstr << endl;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
|
|
|
cerr << "Rename " << oldstr << " => " << newstr << endl;
|
2015-05-07 12:06:49 -04:00
|
|
|
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;
|
|
|
|
return 1;
|
2011-07-14 13:41:06 -04:00
|
|
|
}
|
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
/* Reset path in "session dirs" */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
(*i).path = newstr;
|
2015-04-20 15:10:41 -04:00
|
|
|
(*i).blocks = 0;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
/* reset primary SessionDirectory object */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
if (first) {
|
|
|
|
(*_session_dir) = newstr;
|
2015-01-12 12:57:05 -05:00
|
|
|
new_path = newstr;
|
|
|
|
first = false;
|
2011-07-14 13:41:06 -04:00
|
|
|
}
|
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
/* now rename directory below session_dir/interchange */
|
2011-07-14 13:41:06 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
string old_interchange_dir;
|
|
|
|
string new_interchange_dir;
|
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
/* use newstr here because we renamed the path
|
2015-10-04 12:46:27 -04:00
|
|
|
* (folder/directory) that used to be oldstr to newstr above
|
|
|
|
*/
|
|
|
|
|
|
|
|
v.push_back (newstr);
|
2011-07-14 13:41:06 -04:00
|
|
|
v.push_back (interchange_dir_name);
|
2015-01-12 12:57:05 -05:00
|
|
|
v.push_back (Glib::path_get_basename (oldstr));
|
2011-07-14 13:41:06 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
old_interchange_dir = Glib::build_filename (v);
|
2011-07-14 13:41:06 -04:00
|
|
|
|
|
|
|
v.clear ();
|
|
|
|
v.push_back (newstr);
|
|
|
|
v.push_back (interchange_dir_name);
|
|
|
|
v.push_back (legal_name);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
new_interchange_dir = Glib::build_filename (v);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
cerr << "Rename " << old_interchange_dir << " => " << new_interchange_dir << endl;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
if (::g_rename (old_interchange_dir.c_str(), new_interchange_dir.c_str()) != 0) {
|
2015-05-05 21:30:00 -04:00
|
|
|
cerr << string_compose (_("renaming %s as %2 failed (%3)"),
|
2017-04-20 17:47:39 -04:00
|
|
|
old_interchange_dir, new_interchange_dir,
|
|
|
|
g_strerror (errno))
|
|
|
|
<< endl;
|
2015-01-12 12:57:05 -05:00
|
|
|
error << string_compose (_("renaming %s as %2 failed (%3)"),
|
2017-04-20 17:47:39 -04:00
|
|
|
old_interchange_dir, new_interchange_dir,
|
|
|
|
g_strerror (errno))
|
2015-01-12 12:57:05 -05:00
|
|
|
<< endmsg;
|
2011-07-14 13:41:06 -04:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* state file */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
oldstr = Glib::build_filename (new_path, _current_snapshot_name + statefile_suffix);
|
|
|
|
newstr= Glib::build_filename (new_path, legal_name + statefile_suffix);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
|
|
|
cerr << "Rename " << oldstr << " => " << newstr << endl;
|
2011-07-14 13:41:06 -04:00
|
|
|
|
2014-03-12 08:49:18 -04:00
|
|
|
if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
|
2015-05-05 21:30:00 -04:00
|
|
|
cerr << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
|
2015-04-05 09:40:25 -04:00
|
|
|
error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
|
2011-07-14 13:41:06 -04:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* history file */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
oldstr = Glib::build_filename (new_path, _current_snapshot_name) + history_suffix;
|
2011-07-14 13:41:06 -04:00
|
|
|
|
2011-07-18 11:04:00 -04:00
|
|
|
if (Glib::file_test (oldstr, Glib::FILE_TEST_EXISTS)) {
|
2015-01-12 12:57:05 -05:00
|
|
|
newstr = Glib::build_filename (new_path, legal_name) + history_suffix;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
|
|
|
cerr << "Rename " << oldstr << " => " << newstr << endl;
|
|
|
|
|
2014-03-12 08:49:18 -04:00
|
|
|
if (::g_rename (oldstr.c_str(), newstr.c_str()) != 0) {
|
2015-05-05 21:30:00 -04:00
|
|
|
cerr << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endl;
|
2015-04-05 09:40:25 -04:00
|
|
|
error << string_compose (_("renaming %1 as %2 failed (%3)"), oldstr, newstr, g_strerror (errno)) << endmsg;
|
2011-07-18 11:04:00 -04:00
|
|
|
return 1;
|
|
|
|
}
|
2011-07-14 13:41:06 -04:00
|
|
|
}
|
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
/* remove old name from recent sessions */
|
|
|
|
remove_recent_sessions (_path);
|
|
|
|
_path = new_path;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
/* update file source paths */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 12:06:49 -04:00
|
|
|
for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
|
|
|
|
boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
|
|
|
|
if (fs) {
|
|
|
|
string p = fs->path ();
|
|
|
|
boost::replace_all (p, old_sources_root, _session_dir->sources_root());
|
|
|
|
fs->set_path (p);
|
|
|
|
SourceFactory::setup_peakfile(i->second, true);
|
2011-10-24 15:47:06 -04:00
|
|
|
}
|
2015-01-11 12:15:14 -05:00
|
|
|
}
|
2011-07-18 13:08:50 -04:00
|
|
|
|
2015-11-19 21:10:57 -05:00
|
|
|
set_snapshot_name (new_name);
|
2011-07-14 13:41:06 -04:00
|
|
|
_name = new_name;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
set_dirty ();
|
|
|
|
|
|
|
|
/* save state again to get everything just right */
|
|
|
|
|
|
|
|
save_state (_current_snapshot_name);
|
|
|
|
|
2011-07-18 13:08:50 -04:00
|
|
|
/* add to recent sessions */
|
|
|
|
|
|
|
|
store_recent_sessions (new_name, _path);
|
|
|
|
|
2020-03-24 16:10:14 -04:00
|
|
|
/* remove unnamed file name, if any (it's not an error if it doesn't exist) */
|
|
|
|
::g_unlink (unnamed_file_name().c_str());
|
|
|
|
|
2011-07-14 13:41:06 -04:00
|
|
|
return 0;
|
|
|
|
}
|
2013-09-23 21:35:51 -04:00
|
|
|
|
2017-08-17 12:32:49 -04:00
|
|
|
int
|
|
|
|
Session::parse_stateful_loading_version (const std::string& version)
|
|
|
|
{
|
|
|
|
if (version.empty ()) {
|
|
|
|
/* no version implies very old version of Ardour */
|
|
|
|
return 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (version.find ('.') != string::npos) {
|
|
|
|
/* old school version format */
|
|
|
|
if (version[0] == '2') {
|
|
|
|
return 2000;
|
|
|
|
} else {
|
|
|
|
return 3000;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return string_to<int32_t>(version);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-23 21:35:51 -04:00
|
|
|
int
|
2017-01-20 18:47:33 -05:00
|
|
|
Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFormat& data_format, std::string& program_version)
|
2013-09-23 21:35:51 -04:00
|
|
|
{
|
2016-12-08 04:36:12 -05:00
|
|
|
bool found_sr = false;
|
|
|
|
bool found_data_format = false;
|
2017-08-17 12:32:49 -04:00
|
|
|
std::string version;
|
2017-01-20 18:47:33 -05:00
|
|
|
program_version = "";
|
2016-12-08 04:36:12 -05:00
|
|
|
|
2013-09-23 21:35:51 -04:00
|
|
|
if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
return -1;
|
2016-12-08 04:36:12 -05:00
|
|
|
}
|
2013-09-23 21:35:51 -04:00
|
|
|
|
2016-12-08 04:36:12 -05:00
|
|
|
xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
|
|
|
|
if (ctxt == NULL) {
|
2013-09-23 21:35:51 -04:00
|
|
|
return -1;
|
|
|
|
}
|
2016-12-08 04:36:12 -05:00
|
|
|
xmlDocPtr doc = xmlCtxtReadFile (ctxt, xmlpath.c_str(), NULL, XML_PARSE_HUGE);
|
2013-09-23 21:35:51 -04:00
|
|
|
|
2016-12-08 04:36:12 -05:00
|
|
|
if (doc == NULL) {
|
|
|
|
xmlFreeParserCtxt(ctxt);
|
2013-09-23 21:35:51 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-12-08 04:36:12 -05:00
|
|
|
xmlNodePtr node = xmlDocGetRootElement(doc);
|
2016-05-04 23:09:37 -04:00
|
|
|
|
2016-12-08 04:36:12 -05:00
|
|
|
if (node == NULL) {
|
|
|
|
xmlFreeParserCtxt(ctxt);
|
2017-01-19 19:49:23 -05:00
|
|
|
xmlFreeDoc (doc);
|
2016-12-08 04:36:12 -05:00
|
|
|
return -1;
|
2013-09-23 21:35:51 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 12:32:49 -04:00
|
|
|
/* sample rate & version*/
|
2013-09-25 17:59:10 -04:00
|
|
|
|
2016-12-08 04:36:12 -05:00
|
|
|
xmlAttrPtr attr;
|
|
|
|
for (attr = node->properties; attr; attr = attr->next) {
|
2017-08-17 12:32:49 -04:00
|
|
|
if (!strcmp ((const char*)attr->name, "version") && attr->children) {
|
|
|
|
version = std::string ((char*)attr->children->content);
|
|
|
|
}
|
2016-12-08 04:36:12 -05:00
|
|
|
if (!strcmp ((const char*)attr->name, "sample-rate") && attr->children) {
|
|
|
|
sample_rate = atoi ((char*)attr->children->content);
|
|
|
|
found_sr = true;
|
2013-09-23 21:35:51 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-17 12:32:49 -04:00
|
|
|
if ((parse_stateful_loading_version(version) / 1000L) > (CURRENT_SESSION_FILE_VERSION / 1000L)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-12-16 12:30:09 -05:00
|
|
|
if ((parse_stateful_loading_version(version) / 1000L) <= 2) {
|
|
|
|
/* sample-format '0' is implicit */
|
|
|
|
data_format = FormatFloat;
|
|
|
|
found_data_format = true;
|
|
|
|
}
|
|
|
|
|
2016-12-08 04:36:12 -05:00
|
|
|
node = node->children;
|
|
|
|
while (node != NULL) {
|
2017-01-20 12:15:44 -05:00
|
|
|
if (!strcmp((const char*) node->name, "ProgramVersion")) {
|
2017-01-20 18:47:33 -05:00
|
|
|
xmlChar* val = xmlGetProp (node, (const xmlChar*)"modified-with");
|
2017-01-20 12:15:44 -05:00
|
|
|
if (val) {
|
2017-01-20 18:47:33 -05:00
|
|
|
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);
|
|
|
|
}
|
2017-01-20 12:15:44 -05:00
|
|
|
}
|
|
|
|
xmlFree (val);
|
|
|
|
}
|
2016-12-08 04:36:12 -05:00
|
|
|
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) {
|
2017-08-17 13:28:14 -04:00
|
|
|
try {
|
|
|
|
SampleFormat fmt = (SampleFormat) string_2_enum (string ((const char*)val), fmt);
|
|
|
|
data_format = fmt;
|
|
|
|
found_data_format = true;
|
|
|
|
} catch (PBD::unknown_enumeration& e) {}
|
2016-12-08 04:36:12 -05:00
|
|
|
}
|
|
|
|
xmlFree (val);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
xmlFree (pv);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
xmlFreeParserCtxt(ctxt);
|
2017-01-19 19:49:23 -05:00
|
|
|
xmlFreeDoc (doc);
|
2016-12-08 04:36:12 -05:00
|
|
|
|
2017-08-17 12:32:49 -04:00
|
|
|
return (found_sr && found_data_format) ? 0 : 1;
|
2013-09-23 21:35:51 -04:00
|
|
|
}
|
2014-07-08 00:53:06 -04:00
|
|
|
|
2015-11-19 21:11:39 -05:00
|
|
|
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 (!Glib::file_test (instant_xml_path, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
XMLTree tree;
|
|
|
|
if (!tree.read (instant_xml_path)) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2016-05-04 23:09:37 -04:00
|
|
|
XMLProperty const * prop;
|
2015-11-19 21:11:39 -05:00
|
|
|
XMLNode *last_used_snapshot = tree.root()->child("LastUsedSnapshot");
|
|
|
|
if (last_used_snapshot && (prop = last_used_snapshot->property ("name")) != 0) {
|
|
|
|
return prop->value();
|
|
|
|
}
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
typedef std::vector<boost::shared_ptr<FileSource> > SeveralFileSources;
|
|
|
|
typedef std::map<std::string,SeveralFileSources> SourcePathMap;
|
|
|
|
|
|
|
|
int
|
|
|
|
Session::bring_all_sources_into_session (boost::function<void(uint32_t,uint32_t,string)> callback)
|
|
|
|
{
|
|
|
|
uint32_t total = 0;
|
|
|
|
uint32_t n = 0;
|
|
|
|
SourcePathMap source_path_map;
|
|
|
|
string new_path;
|
|
|
|
boost::shared_ptr<AudioFileSource> afs;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Glib::Threads::Mutex::Lock lm (source_lock);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
cerr << " total sources = " << sources.size();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
|
|
|
|
boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
if (!fs) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
if (fs->within_session()) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
if (source_path_map.find (fs->path()) != source_path_map.end()) {
|
|
|
|
source_path_map[fs->path()].push_back (fs);
|
|
|
|
} else {
|
|
|
|
SeveralFileSources v;
|
|
|
|
v.push_back (fs);
|
|
|
|
source_path_map.insert (make_pair (fs->path(), v));
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
total++;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
cerr << " fsources = " << total << endl;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
for (SourcePathMap::iterator i = source_path_map.begin(); i != source_path_map.end(); ++i) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
/* tell caller where we are */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
string old_path = i->first;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
callback (n, total, old_path);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
cerr << old_path << endl;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
new_path.clear ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
switch (i->second.front()->type()) {
|
|
|
|
case DataType::AUDIO:
|
|
|
|
new_path = new_audio_source_path_for_embedded (old_path);
|
|
|
|
break;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
case DataType::MIDI:
|
2015-04-06 21:18:52 -04:00
|
|
|
/* XXX not implemented yet */
|
2014-07-08 00:53:06 -04:00
|
|
|
break;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-04-06 21:18:52 -04:00
|
|
|
if (new_path.empty()) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
cerr << "Move " << old_path << " => " << new_path << endl;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
if (!copy_file (old_path, new_path)) {
|
|
|
|
cerr << "failed !\n";
|
|
|
|
ret = -1;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
/* 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.
|
|
|
|
*/
|
|
|
|
remove_dir_from_search_path (Glib::path_get_dirname (old_path), i->second.front()->type());
|
|
|
|
|
|
|
|
for (SeveralFileSources::iterator f = i->second.begin(); f != i->second.end(); ++f) {
|
|
|
|
(*f)->set_path (new_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
save_state ("", false, false);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2015-01-11 12:15:14 -05:00
|
|
|
|
|
|
|
static
|
|
|
|
bool accept_all_files (string const &, void *)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
void
|
|
|
|
Session::save_as_bring_callback (uint32_t,uint32_t,string)
|
|
|
|
{
|
|
|
|
/* It would be good if this did something useful vis-a-vis save-as, but the arguments doesn't provide the correct information right now to do this.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
2015-04-06 21:18:52 -04:00
|
|
|
static string
|
2017-10-03 23:06:47 -04:00
|
|
|
make_new_media_path (string old_path, string new_session_folder, string new_session_name)
|
2015-04-06 21:18:52 -04:00
|
|
|
{
|
2017-10-03 23:06:47 -04:00
|
|
|
// old_path must be in within_session ()
|
2015-04-06 21:18:52 -04:00
|
|
|
string typedir = Glib::path_get_basename (Glib::path_get_dirname (old_path));
|
|
|
|
vector<string> v;
|
|
|
|
v.push_back (new_session_folder); /* full path */
|
|
|
|
v.push_back (interchange_dir_name);
|
2017-10-03 23:06:47 -04:00
|
|
|
v.push_back (new_session_name); /* just one directory/folder */
|
2015-04-06 21:18:52 -04:00
|
|
|
v.push_back (typedir);
|
|
|
|
v.push_back (Glib::path_get_basename (old_path));
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-04-06 21:18:52 -04:00
|
|
|
return Glib::build_filename (v);
|
|
|
|
}
|
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
static string
|
|
|
|
make_new_audio_path (string filename, string new_session_folder, string new_session_name)
|
|
|
|
{
|
|
|
|
vector<string> v;
|
|
|
|
v.push_back (new_session_folder); /* full path */
|
|
|
|
v.push_back (interchange_dir_name);
|
|
|
|
v.push_back (new_session_name);
|
|
|
|
v.push_back (ARDOUR::sound_dir_name);
|
|
|
|
v.push_back (filename);
|
|
|
|
|
|
|
|
return Glib::build_filename (v);
|
|
|
|
}
|
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
int
|
|
|
|
Session::save_as (SaveAs& saveas)
|
|
|
|
{
|
|
|
|
vector<string> files;
|
|
|
|
string current_folder = Glib::path_get_dirname (_path);
|
|
|
|
string new_folder = legalize_for_path (saveas.new_name);
|
2015-01-12 12:57:05 -05:00
|
|
|
string to_dir = Glib::build_filename (saveas.new_parent_folder, new_folder);
|
2015-01-11 12:15:14 -05:00
|
|
|
int64_t total_bytes = 0;
|
|
|
|
int64_t copied = 0;
|
|
|
|
int64_t cnt = 0;
|
|
|
|
int64_t all = 0;
|
2015-01-14 17:53:23 -05:00
|
|
|
int32_t internal_file_cnt = 0;
|
|
|
|
|
|
|
|
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);
|
2015-01-11 12:15:14 -05:00
|
|
|
|
|
|
|
/* get total size */
|
|
|
|
|
|
|
|
for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
/* need to clear this because
|
|
|
|
* find_files_matching_filter() is cumulative
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
files.clear ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
all += files.size();
|
2015-01-12 12:57:05 -05:00
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
|
|
|
|
GStatBuf gsb;
|
2015-01-14 17:53:23 -05:00
|
|
|
g_stat ((*i).c_str(), &gsb);
|
|
|
|
total_bytes += gsb.st_size;
|
2015-01-11 12:15:14 -05:00
|
|
|
}
|
2015-01-12 12:57:05 -05:00
|
|
|
}
|
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* save old values so we can switch back if we are not switching to the new session */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
string old_path = _path;
|
|
|
|
string old_name = _name;
|
|
|
|
string old_snapshot = _current_snapshot_name;
|
|
|
|
string old_sd = _session_dir->root_path();
|
2015-01-14 17:53:23 -05:00
|
|
|
vector<string> old_search_path[DataType::num_types];
|
|
|
|
string old_config_search_path[DataType::num_types];
|
|
|
|
|
|
|
|
old_search_path[DataType::AUDIO] = source_search_path (DataType::AUDIO);
|
|
|
|
old_search_path[DataType::MIDI] = source_search_path (DataType::MIDI);
|
2015-10-04 12:46:27 -04:00
|
|
|
old_config_search_path[DataType::AUDIO] = config.get_audio_search_path ();
|
|
|
|
old_config_search_path[DataType::MIDI] = config.get_midi_search_path ();
|
2015-01-14 17:53:23 -05:00
|
|
|
|
|
|
|
/* switch session directory */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
(*_session_dir) = to_dir;
|
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* create new tree */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
if (!_session_dir->create()) {
|
2015-01-15 09:11:56 -05:00
|
|
|
saveas.failure_message = string_compose (_("Cannot create new session folder %1"), to_dir);
|
2015-01-12 12:57:05 -05:00
|
|
|
return -1;
|
2015-01-11 12:15:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2015-05-27 10:57:40 -04:00
|
|
|
/* copy all relevant files. Find each location in session_dirs,
|
|
|
|
* and copy files from there to target.
|
2015-01-14 17:53:23 -05:00
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
for (vector<space_and_path>::const_iterator sd = session_dirs.begin(); sd != session_dirs.end(); ++sd) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* need to clear this because
|
|
|
|
* find_files_matching_filter() is cumulative
|
2015-01-12 12:57:05 -05:00
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
files.clear ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
const size_t prefix_len = (*sd).path.size();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* Work just on the files within this session dir */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
find_files_matching_filter (files, (*sd).path, accept_all_files, 0, false, true, true);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
/* add dir separator to protect against collisions with
|
|
|
|
* track names (e.g. track named "audiofiles" or
|
|
|
|
* "analysis".
|
|
|
|
*/
|
|
|
|
|
|
|
|
static const std::string audiofile_dir_string = string (sound_dir_name) + G_DIR_SEPARATOR;
|
|
|
|
static const std::string midifile_dir_string = string (midi_dir_name) + G_DIR_SEPARATOR;
|
|
|
|
static const std::string analysis_dir_string = analysis_dir() + G_DIR_SEPARATOR;
|
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* copy all the files. Handling is different for media files
|
|
|
|
than others because of the *silly* subtree we have below the interchange
|
|
|
|
folder. That really was a bad idea, but I'm not fixing it as part of
|
|
|
|
implementing ::save_as().
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
|
2015-01-11 12:15:14 -05:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
std::string from = *i;
|
2015-05-28 19:26:21 -04:00
|
|
|
|
2015-04-06 14:25:42 -04:00
|
|
|
#ifdef __APPLE__
|
|
|
|
string filename = Glib::path_get_basename (from);
|
|
|
|
std::transform (filename.begin(), filename.end(), filename.begin(), ::toupper);
|
|
|
|
if (filename == ".DS_STORE") {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#endif
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-04-06 14:25:42 -04:00
|
|
|
if (from.find (audiofile_dir_string) != string::npos) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
/* audio file: only copy if asked */
|
2015-01-12 12:57:05 -05:00
|
|
|
|
2015-05-27 10:57:40 -04:00
|
|
|
if (saveas.include_media && saveas.copy_media) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-04-06 21:18:52 -04:00
|
|
|
string to = make_new_media_path (*i, to_dir, new_folder);
|
|
|
|
|
2015-05-27 10:57:40 -04:00
|
|
|
info << "media file copying from " << from << " to " << to << endmsg;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
if (!copy_file (from, to)) {
|
2015-04-02 19:15:35 -04:00
|
|
|
throw Glib::FileError (Glib::FileError::IO_ERROR,
|
|
|
|
string_compose(_("\ncopying \"%1\" failed !"), from));
|
2015-01-12 12:57:05 -05:00
|
|
|
}
|
2015-01-14 17:53:23 -05:00
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* we found media files inside the session folder */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
internal_file_cnt++;
|
2015-05-28 19:26:21 -04:00
|
|
|
|
2015-04-06 14:25:42 -04:00
|
|
|
} else if (from.find (midifile_dir_string) != string::npos) {
|
2015-05-28 19:26:21 -04:00
|
|
|
|
|
|
|
/* midi file: always copy unless
|
|
|
|
* creating an empty new session
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (saveas.include_media) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
string to = make_new_media_path (*i, to_dir, new_folder);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
info << "media file copying from " << from << " to " << to << endmsg;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
if (!copy_file (from, to)) {
|
|
|
|
throw Glib::FileError (Glib::FileError::IO_ERROR, "copy failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* we found media files inside the session folder */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
internal_file_cnt++;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-04-06 14:25:42 -04:00
|
|
|
} else if (from.find (analysis_dir_string) != string::npos) {
|
2015-05-28 19:26:21 -04:00
|
|
|
|
|
|
|
/* make sure analysis dir exists in
|
|
|
|
* new session folder, but we're not
|
|
|
|
* copying analysis files here, see
|
2015-10-04 12:46:27 -04:00
|
|
|
* below
|
2015-05-28 19:26:21 -04:00
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
(void) g_mkdir_with_parents (analysis_dir().c_str(), 775);
|
|
|
|
continue;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
} else {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* normal non-media file. Don't copy state, history, etc.
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
bool do_copy = true;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
for (vector<string>::iterator v = do_not_copy_extensions.begin(); v != do_not_copy_extensions.end(); ++v) {
|
2015-04-06 14:25:42 -04:00
|
|
|
if ((from.length() > (*v).length()) && (from.find (*v) == from.length() - (*v).length())) {
|
2015-01-14 17:53:23 -05:00
|
|
|
/* end of filename matches extension, do not copy file */
|
|
|
|
do_copy = false;
|
|
|
|
break;
|
2015-10-04 12:46:27 -04:00
|
|
|
}
|
2015-01-14 17:53:23 -05:00
|
|
|
}
|
2015-05-28 19:26:21 -04:00
|
|
|
|
2015-04-06 14:25:42 -04:00
|
|
|
if (!saveas.copy_media && from.find (peakfile_suffix) != string::npos) {
|
2015-05-28 19:26:21 -04:00
|
|
|
/* don't copy peakfiles if
|
|
|
|
* we're not copying media
|
|
|
|
*/
|
|
|
|
do_copy = false;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
if (do_copy) {
|
2015-04-06 14:25:42 -04:00
|
|
|
string to = Glib::build_filename (to_dir, from.substr (prefix_len));
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-27 10:57:40 -04:00
|
|
|
info << "attempting to make directory/folder " << to << endmsg;
|
|
|
|
|
2015-04-06 21:18:52 -04:00
|
|
|
if (g_mkdir_with_parents (Glib::path_get_dirname (to).c_str(), 0755)) {
|
|
|
|
throw Glib::FileError (Glib::FileError::IO_ERROR, "cannot create required directory");
|
|
|
|
}
|
2015-05-27 10:57:40 -04:00
|
|
|
|
|
|
|
info << "attempting to copy " << from << " to " << to << endmsg;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
if (!copy_file (from, to)) {
|
2015-04-02 19:15:35 -04:00
|
|
|
throw Glib::FileError (Glib::FileError::IO_ERROR,
|
|
|
|
string_compose(_("\ncopying \"%1\" failed !"), from));
|
2015-01-12 12:57:05 -05:00
|
|
|
}
|
2015-01-11 12:15:14 -05:00
|
|
|
}
|
2015-01-12 12:57:05 -05:00
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* 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.
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
GStatBuf gsb;
|
2015-04-06 14:25:42 -04:00
|
|
|
g_stat (from.c_str(), &gsb);
|
2015-01-14 17:53:23 -05:00
|
|
|
copied += gsb.st_size;
|
|
|
|
cnt++;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
double fraction = (double) copied / total_bytes;
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
bool keep_going = true;
|
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
if (saveas.copy_media) {
|
|
|
|
|
|
|
|
/* no need or expectation of this if
|
|
|
|
* media is not being copied, because
|
|
|
|
* it will be fast(ish).
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
/* tell someone "X percent, file M of N"; M is one-based */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
boost::optional<bool> res = saveas.Progress (fraction, cnt, all);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
if (res) {
|
|
|
|
keep_going = *res;
|
|
|
|
}
|
2015-01-14 17:53:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!keep_going) {
|
|
|
|
throw Glib::FileError (Glib::FileError::FAILED, "copy cancelled");
|
|
|
|
}
|
2015-01-12 12:57:05 -05:00
|
|
|
}
|
2015-04-06 21:18:52 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
}
|
|
|
|
|
2015-05-07 17:10:14 -04:00
|
|
|
/* copy optional folders, if any */
|
|
|
|
|
|
|
|
string old = plugins_dir ();
|
|
|
|
if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
string newdir = Glib::build_filename (to_dir, Glib::path_get_basename (old));
|
|
|
|
copy_files (old, newdir);
|
|
|
|
}
|
|
|
|
|
|
|
|
old = externals_dir ();
|
|
|
|
if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
string newdir = Glib::build_filename (to_dir, Glib::path_get_basename (old));
|
|
|
|
copy_files (old, newdir);
|
|
|
|
}
|
|
|
|
|
|
|
|
old = automation_dir ();
|
|
|
|
if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
string newdir = Glib::build_filename (to_dir, Glib::path_get_basename (old));
|
|
|
|
copy_files (old, newdir);
|
|
|
|
}
|
|
|
|
|
2015-05-07 22:35:35 -04:00
|
|
|
if (saveas.include_media) {
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 22:35:35 -04:00
|
|
|
if (saveas.copy_media) {
|
2015-05-28 19:26:21 -04:00
|
|
|
#ifndef PLATFORM_WINDOWS
|
|
|
|
/* There are problems with analysis files on
|
|
|
|
* Windows, because they used a colon in their
|
|
|
|
* names as late as 4.0. Colons are not legal
|
|
|
|
* under Windows even if NTFS allows them.
|
|
|
|
*
|
|
|
|
* This is a tricky problem to solve so for
|
|
|
|
* just don't copy these files. They will be
|
2015-10-04 12:46:27 -04:00
|
|
|
* regenerated as-needed anyway, subject to the
|
2015-05-28 19:26:21 -04:00
|
|
|
* existing issue that the filenames will be
|
|
|
|
* rejected by Windows, which is a separate
|
|
|
|
* problem (though related).
|
|
|
|
*/
|
|
|
|
|
2015-05-07 22:35:35 -04:00
|
|
|
/* only needed if we are copying media, since the
|
|
|
|
* analysis data refers to media data
|
|
|
|
*/
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-05-07 22:35:35 -04:00
|
|
|
old = analysis_dir ();
|
|
|
|
if (Glib::file_test (old, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
string newdir = Glib::build_filename (to_dir, "analysis");
|
|
|
|
copy_files (old, newdir);
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
#endif /* PLATFORM_WINDOWS */
|
2015-05-07 17:10:14 -04:00
|
|
|
}
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
_path = to_dir;
|
2015-11-19 21:10:57 -05:00
|
|
|
set_snapshot_name (saveas.new_name);
|
2015-01-12 12:57:05 -05:00
|
|
|
_name = saveas.new_name;
|
|
|
|
|
2015-05-07 22:35:35 -04:00
|
|
|
if (saveas.include_media && !saveas.copy_media) {
|
2015-01-12 12:57:05 -05:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* reset search paths of the new session (which we're pretending to be right now) to
|
|
|
|
include the original session search path, so we can still find all audio.
|
|
|
|
*/
|
2015-01-12 12:57:05 -05:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
if (internal_file_cnt) {
|
|
|
|
for (vector<string>::iterator s = old_search_path[DataType::AUDIO].begin(); s != old_search_path[DataType::AUDIO].end(); ++s) {
|
|
|
|
ensure_search_path_includes (*s, DataType::AUDIO);
|
2015-05-27 10:57:40 -04:00
|
|
|
cerr << "be sure to include " << *s << " for audio" << endl;
|
2015-01-14 17:53:23 -05:00
|
|
|
}
|
2015-01-11 12:15:14 -05:00
|
|
|
|
2015-05-28 19:26:21 -04:00
|
|
|
/* we do not do this for MIDI because we copy
|
|
|
|
all MIDI files if saveas.include_media is
|
|
|
|
true
|
|
|
|
*/
|
2015-01-11 12:15:14 -05:00
|
|
|
}
|
2015-01-12 12:57:05 -05:00
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
bool was_dirty = dirty ();
|
|
|
|
|
|
|
|
save_default_options ();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
if (saveas.copy_media && saveas.copy_external) {
|
2015-01-14 17:53:23 -05:00
|
|
|
if (bring_all_sources_into_session (boost::bind (&Session::save_as_bring_callback, this, _1, _2, _3))) {
|
2015-01-12 12:57:05 -05:00
|
|
|
throw Glib::FileError (Glib::FileError::NO_SPACE_LEFT, "consolidate failed");
|
2015-01-11 12:15:14 -05:00
|
|
|
}
|
2015-01-12 12:57:05 -05:00
|
|
|
}
|
|
|
|
|
2015-05-07 22:35:35 -04:00
|
|
|
saveas.final_session_folder_name = _path;
|
2015-05-08 12:12:08 -04:00
|
|
|
|
|
|
|
store_recent_sessions (_name, _path);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
if (!saveas.switch_to) {
|
|
|
|
|
2017-02-09 06:25:09 -05:00
|
|
|
/* save the new state */
|
|
|
|
|
|
|
|
save_state ("", false, false, !saveas.include_media);
|
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
/* switch back to the way things were */
|
|
|
|
|
|
|
|
_path = old_path;
|
|
|
|
_name = old_name;
|
2015-11-19 21:10:57 -05:00
|
|
|
set_snapshot_name (old_snapshot);
|
2015-01-12 12:57:05 -05:00
|
|
|
|
|
|
|
(*_session_dir) = old_sd;
|
|
|
|
|
|
|
|
if (was_dirty) {
|
|
|
|
set_dirty ();
|
2015-01-11 12:15:14 -05:00
|
|
|
}
|
2015-01-12 12:57:05 -05:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
if (internal_file_cnt) {
|
|
|
|
/* reset these to their original values */
|
|
|
|
config.set_audio_search_path (old_config_search_path[DataType::AUDIO]);
|
|
|
|
config.set_midi_search_path (old_config_search_path[DataType::MIDI]);
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
} else {
|
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* prune session dirs, and update disk space statistics
|
2015-01-12 12:57:05 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
space_and_path sp;
|
|
|
|
sp.path = _path;
|
|
|
|
session_dirs.clear ();
|
|
|
|
session_dirs.push_back (sp);
|
|
|
|
refresh_disk_space ();
|
2015-04-06 12:05:36 -04:00
|
|
|
|
2017-05-12 18:57:58 -04:00
|
|
|
_writable = exists_and_writable (_path);
|
|
|
|
|
2015-10-04 12:46:27 -04:00
|
|
|
/* ensure that all existing tracks reset their current capture source paths
|
2015-04-06 12:05:36 -04:00
|
|
|
*/
|
|
|
|
reset_write_sources (true, true);
|
2015-04-06 21:18:52 -04:00
|
|
|
|
2017-02-09 06:25:09 -05:00
|
|
|
/* 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) {
|
2019-03-18 10:33:05 -04:00
|
|
|
unset_dirty ();
|
2017-02-09 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
save_state ("", false, false, !saveas.include_media);
|
|
|
|
|
2015-04-06 21:18:52 -04:00
|
|
|
/* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
|
|
|
|
boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
|
2015-04-21 00:18:49 -04:00
|
|
|
|
2015-04-06 21:18:52 -04:00
|
|
|
if (!fs) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-04-21 00:18:49 -04:00
|
|
|
if (fs->within_session()) {
|
|
|
|
string newpath = make_new_media_path (fs->path(), to_dir, new_folder);
|
|
|
|
fs->set_path (newpath);
|
|
|
|
}
|
2015-04-06 21:18:52 -04:00
|
|
|
}
|
2015-01-11 12:15:14 -05:00
|
|
|
}
|
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
} catch (Glib::FileError& e) {
|
|
|
|
|
|
|
|
saveas.failure_message = e.what();
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* recursively remove all the directories */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
remove_directory (to_dir);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
/* return error */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
return -1;
|
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
} catch (...) {
|
2015-01-12 12:57:05 -05:00
|
|
|
|
2015-01-14 17:53:23 -05:00
|
|
|
saveas.failure_message = _("unknown reason");
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
/* recursively remove all the directories */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-12 12:57:05 -05:00
|
|
|
remove_directory (to_dir);
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
/* return error */
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
return -1;
|
|
|
|
}
|
2015-10-04 12:46:27 -04:00
|
|
|
|
2015-01-11 12:15:14 -05:00
|
|
|
return 0;
|
|
|
|
}
|
2016-08-17 19:21:45 -04:00
|
|
|
|
2016-09-20 13:46:07 -04:00
|
|
|
static void set_progress (Progress* p, size_t n, size_t t)
|
|
|
|
{
|
|
|
|
p->set_progress (float (n) / float(t));
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2016-09-23 14:34:28 -04:00
|
|
|
Session::archive_session (const std::string& dest,
|
|
|
|
const std::string& name,
|
|
|
|
ArchiveEncode compress_audio,
|
2017-10-02 19:56:17 -04:00
|
|
|
FileArchive::CompressionLevel compression_level,
|
2016-09-23 14:34:28 -04:00
|
|
|
bool only_used_sources,
|
|
|
|
Progress* progress)
|
2016-09-20 13:46:07 -04:00
|
|
|
{
|
|
|
|
if (dest.empty () || name.empty ()) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
/* We are going to temporarily change some source properties,
|
|
|
|
* don't allow any concurrent saves (periodic or otherwise */
|
|
|
|
Glib::Threads::Mutex::Lock lm (save_source_lock);
|
|
|
|
|
|
|
|
disable_record (false);
|
|
|
|
|
2016-09-20 13:46:07 -04:00
|
|
|
/* save current values */
|
|
|
|
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 */
|
2017-10-02 19:06:07 -04:00
|
|
|
string archive = Glib::build_filename (dest, name + session_archive_suffix);
|
2016-09-20 13:46:07 -04:00
|
|
|
|
|
|
|
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);
|
2016-09-20 22:05:45 -04:00
|
|
|
blacklist_dirs.push_back (string (plugins_dir_name) + G_DIR_SEPARATOR);
|
2016-09-20 13:46:07 -04:00
|
|
|
|
2016-09-20 22:05:45 -04:00
|
|
|
std::map<boost::shared_ptr<AudioFileSource>, std::string> orig_sources;
|
2017-10-03 23:06:47 -04:00
|
|
|
std::map<boost::shared_ptr<AudioFileSource>, std::string> orig_origin;
|
2016-12-27 12:33:41 -05:00
|
|
|
std::map<boost::shared_ptr<AudioFileSource>, float> orig_gain;
|
2016-09-20 22:05:45 -04:00
|
|
|
|
2016-09-23 14:34:28 -04:00
|
|
|
set<boost::shared_ptr<Source> > sources_used_by_this_snapshot;
|
|
|
|
if (only_used_sources) {
|
2019-03-19 00:14:00 -04:00
|
|
|
_playlists->sync_all_regions_with_regions ();
|
|
|
|
_playlists->foreach (boost::bind (merge_all_sources, _1, &sources_used_by_this_snapshot), false);
|
2016-09-23 14:34:28 -04:00
|
|
|
}
|
2016-09-21 07:04:25 -04:00
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
/* collect audio sources for this session, calc total size for encoding
|
|
|
|
* add option to only include *used* sources (see Session::cleanup_sources)
|
|
|
|
*/
|
2016-09-23 14:34:28 -04:00
|
|
|
size_t total_size = 0;
|
|
|
|
{
|
2016-09-20 22:05:45 -04:00
|
|
|
Glib::Threads::Mutex::Lock lm (source_lock);
|
2017-10-03 23:06:47 -04:00
|
|
|
|
|
|
|
/* build a list of used names */
|
|
|
|
std::set<std::string> audio_file_names;
|
|
|
|
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
|
2018-12-05 10:05:56 -05:00
|
|
|
if (boost::dynamic_pointer_cast<SilentFileSource> (i->second)) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-10-03 23:06:47 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
audio_file_names.insert (Glib::path_get_basename (afs->path()));
|
|
|
|
}
|
|
|
|
|
2016-09-23 14:34:28 -04:00
|
|
|
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
|
2018-12-05 10:05:56 -05:00
|
|
|
if (boost::dynamic_pointer_cast<SilentFileSource> (i->second)) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-09-23 14:34:28 -04:00
|
|
|
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()) {
|
2016-09-21 07:04:25 -04:00
|
|
|
continue;
|
|
|
|
}
|
2016-09-23 14:34:28 -04:00
|
|
|
if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
|
2016-09-21 07:04:25 -04:00
|
|
|
continue;
|
|
|
|
}
|
2016-09-23 14:34:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string from = afs->path();
|
|
|
|
|
|
|
|
if (compress_audio != NO_ENCODE) {
|
2016-09-21 07:04:25 -04:00
|
|
|
total_size += afs->readable_length ();
|
2016-09-23 14:34:28 -04:00
|
|
|
} else {
|
2017-10-03 23:06:47 -04:00
|
|
|
/* copy files as-is */
|
|
|
|
if (!afs->within_session()) {
|
|
|
|
string to = Glib::path_get_basename (from);
|
|
|
|
|
|
|
|
/* avoid name collitions, see also new_audio_source_path_for_embedded ()
|
|
|
|
* - avoid conflict with files existing in interchange
|
|
|
|
* - avoid conflict with other embedded sources
|
|
|
|
*/
|
|
|
|
if (audio_file_names.find (to) == audio_file_names.end ()) {
|
|
|
|
// we need a new name, add a '-<num>' before the '.<ext>'
|
|
|
|
string bn = to.substr (0, to.find_last_of ('.'));
|
|
|
|
string ext = to.find_last_of ('.') == string::npos ? "" : to.substr (to.find_last_of ('.'));
|
|
|
|
to = bn + "-1" + ext;
|
|
|
|
}
|
|
|
|
while (audio_file_names.find (to) == audio_file_names.end ()) {
|
|
|
|
to = bump_name_once (to, '-');
|
|
|
|
}
|
|
|
|
|
|
|
|
audio_file_names.insert (to);
|
|
|
|
filemap[from] = make_new_audio_path (to, name, name);
|
|
|
|
|
|
|
|
remove_dir_from_search_path (Glib::path_get_dirname (from), DataType::AUDIO);
|
|
|
|
|
|
|
|
orig_origin[afs] = afs->origin ();
|
|
|
|
afs->set_origin ("");
|
|
|
|
|
2016-09-23 14:34:28 -04:00
|
|
|
} else {
|
|
|
|
filemap[from] = make_new_media_path (from, name, name);
|
|
|
|
}
|
2016-09-21 07:04:25 -04:00
|
|
|
}
|
2016-09-23 14:34:28 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* encode audio */
|
|
|
|
if (compress_audio != NO_ENCODE) {
|
|
|
|
if (progress) {
|
2016-09-21 07:04:25 -04:00
|
|
|
progress->set_progress (2); // set to "encoding"
|
|
|
|
progress->set_progress (0);
|
|
|
|
}
|
|
|
|
|
2016-09-23 14:34:28 -04:00
|
|
|
Glib::Threads::Mutex::Lock lm (source_lock);
|
2016-09-20 22:05:45 -04:00
|
|
|
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
|
2018-12-05 10:05:56 -05:00
|
|
|
if (boost::dynamic_pointer_cast<SilentFileSource> (i->second)) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-09-20 22:05:45 -04:00
|
|
|
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource> (i->second);
|
2016-09-23 14:34:28 -04:00
|
|
|
if (!afs || afs->readable_length () == 0) {
|
2016-09-20 22:05:45 -04:00
|
|
|
continue;
|
|
|
|
}
|
2016-09-23 14:34:28 -04:00
|
|
|
|
|
|
|
if (only_used_sources) {
|
|
|
|
if (!afs->used()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (sources_used_by_this_snapshot.find (afs) == sources_used_by_this_snapshot.end ()) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-09-20 22:05:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
orig_sources[afs] = afs->path();
|
2016-12-27 12:33:41 -05:00
|
|
|
orig_gain[afs] = afs->gain();
|
2016-09-20 22:05:45 -04:00
|
|
|
|
|
|
|
std::string new_path = make_new_media_path (afs->path (), to_dir, name);
|
2017-10-02 19:06:07 -04:00
|
|
|
|
|
|
|
std::string channelsuffix = "";
|
|
|
|
if (afs->channel() > 0) { /* n_channels() is /wrongly/ 1. */
|
|
|
|
/* embedded external multi-channel files are converted to multiple-mono */
|
|
|
|
channelsuffix = string_compose ("-c%1", afs->channel ());
|
|
|
|
}
|
|
|
|
new_path = Glib::build_filename (Glib::path_get_dirname (new_path), PBD::basename_nosuffix (new_path) + channelsuffix + ".flac");
|
2016-09-20 22:05:45 -04:00
|
|
|
g_mkdir_with_parents (Glib::path_get_dirname (new_path).c_str (), 0755);
|
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
/* avoid name collisions of external files with same name */
|
|
|
|
if (Glib::file_test (new_path, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
new_path = Glib::build_filename (Glib::path_get_dirname (new_path), PBD::basename_nosuffix (new_path) + channelsuffix + "-1.flac");
|
|
|
|
}
|
|
|
|
while (Glib::file_test (new_path, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
new_path = bump_name_once (new_path, '-');
|
|
|
|
}
|
2017-10-02 19:06:07 -04:00
|
|
|
|
2016-09-21 07:04:25 -04:00
|
|
|
if (progress) {
|
|
|
|
progress->descend ((float)afs->readable_length () / total_size);
|
|
|
|
}
|
2016-09-23 14:34:28 -04:00
|
|
|
|
2016-09-20 22:05:45 -04:00
|
|
|
try {
|
2016-09-21 07:04:25 -04:00
|
|
|
SndFileSource* ns = new SndFileSource (*this, *(afs.get()), new_path, compress_audio == FLAC_16BIT, progress);
|
2016-09-20 22:05:45 -04:00
|
|
|
afs->replace_file (new_path);
|
2016-12-27 12:33:41 -05:00
|
|
|
afs->set_gain (ns->gain(), true);
|
2016-09-20 22:05:45 -04:00
|
|
|
delete ns;
|
|
|
|
} catch (...) {
|
|
|
|
cerr << "failed to encode " << afs->path() << " to " << new_path << "\n";
|
|
|
|
}
|
2016-09-23 14:34:28 -04:00
|
|
|
|
2016-09-21 07:04:25 -04:00
|
|
|
if (progress) {
|
|
|
|
progress->ascend ();
|
|
|
|
}
|
2016-09-20 22:05:45 -04:00
|
|
|
}
|
|
|
|
}
|
2016-09-20 13:46:07 -04:00
|
|
|
|
2016-09-21 07:04:25 -04:00
|
|
|
if (progress) {
|
|
|
|
progress->set_progress (-1); // set to "archiving"
|
|
|
|
progress->set_progress (0);
|
|
|
|
}
|
|
|
|
|
2016-09-20 13:46:07 -04:00
|
|
|
/* 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) {
|
2016-09-23 14:34:28 -04:00
|
|
|
; // handled above
|
2016-09-20 13:46:07 -04:00
|
|
|
} 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);
|
2017-10-02 19:06:07 -04:00
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
save_state (name, false, false, false, true, only_used_sources);
|
2017-10-02 19:06:07 -04:00
|
|
|
|
2016-09-20 13:46:07 -04:00
|
|
|
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;
|
|
|
|
config.set_audio_search_path (old_config_search_path[DataType::AUDIO]);
|
|
|
|
config.set_midi_search_path (old_config_search_path[DataType::MIDI]);
|
|
|
|
|
2017-10-03 23:06:47 -04:00
|
|
|
for (std::map<boost::shared_ptr<AudioFileSource>, std::string>::iterator i = orig_origin.begin (); i != orig_origin.end (); ++i) {
|
|
|
|
i->first->set_origin (i->second);
|
|
|
|
}
|
2016-09-20 22:05:45 -04:00
|
|
|
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);
|
|
|
|
}
|
2016-12-27 12:33:41 -05:00
|
|
|
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);
|
|
|
|
}
|
2016-09-20 22:05:45 -04:00
|
|
|
|
2017-10-02 19:56:17 -04:00
|
|
|
int rv = ar.create (filemap, compression_level);
|
2016-09-20 13:46:07 -04:00
|
|
|
remove_directory (to_dir);
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2016-08-17 19:21:45 -04:00
|
|
|
void
|
|
|
|
Session::undo (uint32_t n)
|
|
|
|
{
|
|
|
|
if (actively_recording()) {
|
|
|
|
return;
|
|
|
|
}
|
Fix recursive locks during undo/redo due to state save
undo may restore locations, which may trigger a state-save.
This can result in a deadlock:
Location::set_state () -> Locations::get_state()
both acquire a the same lock:
#2 0x000055a8421836d0 in Glib::Threads::Mutex::Lock::Lock(Glib::Threads::Mutex&) (this=0x7ffe38dcad40, mutex=...) at /usr/include/glibmm-2.4/glibmm/threads.h:687
#3 0x00007fc637731e9c in ARDOUR::Locations::get_state() (this=0x55a8466d4740) at ../libs/ardour/location.cc:1075
#4 0x00007fc637bf14b7 in ARDOUR::Session::state(bool, ARDOUR::Session::snapshot_t, bool)
(this=0x55a846d0f050, save_template=false, snapshot_type=ARDOUR::Session::NormalSave, only_used_assets=false) at ../libs/ardour/session_state.cc:1406
#5 0x00007fc637bed2c8 in ARDOUR::Session::save_state(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, bool, bool, bool, bool)
(this=0x55a846d0f050, snapshot_name="", pending=true, switch_to_snapshot=false, template_only=false, for_archive=false, only_used_assets=false) at ../libs/ardour/session_state.cc:815
#6 0x00007fc637b4d967 in ARDOUR::Session::auto_punch_start_changed(ARDOUR::Location*) (this=0x55a846d0f050, location=0x55a848fe11d0) at ../libs/ardour/session.cc:1395
#7 0x00007fc637b4da21 in ARDOUR::Session::auto_punch_end_changed(ARDOUR::Location*)
(this=0x7fc637b4da21 <ARDOUR::Session::auto_punch_end_changed(ARDOUR::Location*)+67>, location=0x7ffe38dcbf10) at ../libs/ardour/session.cc:1403
[..]
#14 0x00007fc637730a2a in ARDOUR::Location::set_state(XMLNode const&, int) (this=0x55a848fe11d0, node=..., version=6000) at ../libs/ardour/location.cc:715
#15 0x00007fc637732428 in ARDOUR::Locations::set_state(XMLNode const&, int) (this=0x55a8466d4740, node=..., version=6000) at ../libs/ardour/location.cc:1130
#16 0x000055a842388dd7 in MementoCommand<ARDOUR::Locations>::undo() (this=0x55a84d1e5f10) at ../libs/pbd/pbd/memento_command.h:141
#17 0x00007fc635b50707 in UndoTransaction::undo() (this=0x55a84d513b80) at ../libs/pbd/undo.cc:128
#18 0x00007fc635b50e1c in UndoHistory::undo(unsigned int) (this=0x55a846d11338, n=0) at ../libs/pbd/undo.cc:267
#19 0x00007fc637c0dfff in ARDOUR::Session::undo(unsigned int) (this=0x55a846d0f050, n=1) at ../libs/ardour/session_state.cc:5577
2020-01-29 22:16:19 -05:00
|
|
|
StateProtector stp (this);
|
2016-08-17 19:21:45 -04:00
|
|
|
_history.undo (n);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Session::redo (uint32_t n)
|
|
|
|
{
|
|
|
|
if (actively_recording()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
Fix recursive locks during undo/redo due to state save
undo may restore locations, which may trigger a state-save.
This can result in a deadlock:
Location::set_state () -> Locations::get_state()
both acquire a the same lock:
#2 0x000055a8421836d0 in Glib::Threads::Mutex::Lock::Lock(Glib::Threads::Mutex&) (this=0x7ffe38dcad40, mutex=...) at /usr/include/glibmm-2.4/glibmm/threads.h:687
#3 0x00007fc637731e9c in ARDOUR::Locations::get_state() (this=0x55a8466d4740) at ../libs/ardour/location.cc:1075
#4 0x00007fc637bf14b7 in ARDOUR::Session::state(bool, ARDOUR::Session::snapshot_t, bool)
(this=0x55a846d0f050, save_template=false, snapshot_type=ARDOUR::Session::NormalSave, only_used_assets=false) at ../libs/ardour/session_state.cc:1406
#5 0x00007fc637bed2c8 in ARDOUR::Session::save_state(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, bool, bool, bool, bool)
(this=0x55a846d0f050, snapshot_name="", pending=true, switch_to_snapshot=false, template_only=false, for_archive=false, only_used_assets=false) at ../libs/ardour/session_state.cc:815
#6 0x00007fc637b4d967 in ARDOUR::Session::auto_punch_start_changed(ARDOUR::Location*) (this=0x55a846d0f050, location=0x55a848fe11d0) at ../libs/ardour/session.cc:1395
#7 0x00007fc637b4da21 in ARDOUR::Session::auto_punch_end_changed(ARDOUR::Location*)
(this=0x7fc637b4da21 <ARDOUR::Session::auto_punch_end_changed(ARDOUR::Location*)+67>, location=0x7ffe38dcbf10) at ../libs/ardour/session.cc:1403
[..]
#14 0x00007fc637730a2a in ARDOUR::Location::set_state(XMLNode const&, int) (this=0x55a848fe11d0, node=..., version=6000) at ../libs/ardour/location.cc:715
#15 0x00007fc637732428 in ARDOUR::Locations::set_state(XMLNode const&, int) (this=0x55a8466d4740, node=..., version=6000) at ../libs/ardour/location.cc:1130
#16 0x000055a842388dd7 in MementoCommand<ARDOUR::Locations>::undo() (this=0x55a84d1e5f10) at ../libs/pbd/pbd/memento_command.h:141
#17 0x00007fc635b50707 in UndoTransaction::undo() (this=0x55a84d513b80) at ../libs/pbd/undo.cc:128
#18 0x00007fc635b50e1c in UndoHistory::undo(unsigned int) (this=0x55a846d11338, n=0) at ../libs/pbd/undo.cc:267
#19 0x00007fc637c0dfff in ARDOUR::Session::undo(unsigned int) (this=0x55a846d0f050, n=1) at ../libs/ardour/session_state.cc:5577
2020-01-29 22:16:19 -05:00
|
|
|
StateProtector stp (this);
|
2016-08-17 19:21:45 -04:00
|
|
|
_history.redo (n);
|
|
|
|
}
|
2020-03-24 12:38:03 -04:00
|
|
|
|
2020-03-24 15:20:24 -04:00
|
|
|
std::string
|
|
|
|
Session::unnamed_file_name() const
|
|
|
|
{
|
|
|
|
return Glib::build_filename (_path, X_(".unnamed"));
|
|
|
|
}
|
|
|
|
|
2020-03-24 12:38:03 -04:00
|
|
|
bool
|
2020-03-24 16:10:14 -04:00
|
|
|
Session::unnamed() const
|
2020-03-24 12:38:03 -04:00
|
|
|
{
|
2020-03-24 15:20:24 -04:00
|
|
|
return Glib::file_test (unnamed_file_name(), Glib::FILE_TEST_EXISTS);
|
2020-03-24 12:38:03 -04:00
|
|
|
}
|
2020-03-25 12:24:47 -04:00
|
|
|
|
|
|
|
void
|
|
|
|
Session::end_unnamed_status () const
|
|
|
|
{
|
|
|
|
::g_remove (unnamed_file_name().c_str());
|
|
|
|
}
|