d819b922e1
(begin|commit)_reversible_command in Editor and Session git-svn-id: svn://localhost/ardour2/branches/undo@684 d708f5d6-7413-0410-9779-e7cbd77b26cf
3234 lines
73 KiB
C++
3234 lines
73 KiB
C++
/*
|
|
Copyright (C) 1999-2002 Paul Davis
|
|
|
|
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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
$Id$
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <cerrno>
|
|
|
|
#include <sigc++/bind.h>
|
|
|
|
#include <cstdio> /* snprintf(3) ... grrr */
|
|
#include <cmath>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <climits>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/time.h>
|
|
#include <dirent.h>
|
|
|
|
#ifdef HAVE_SYS_VFS_H
|
|
#include <sys/vfs.h>
|
|
#else
|
|
#include <sys/mount.h>
|
|
#include <sys/param.h>
|
|
#endif
|
|
|
|
#include <glibmm.h>
|
|
|
|
#include <midi++/mmc.h>
|
|
#include <midi++/port.h>
|
|
#include <pbd/error.h>
|
|
|
|
#include <glibmm/thread.h>
|
|
#include <pbd/pathscanner.h>
|
|
#include <pbd/pthread_utils.h>
|
|
#include <pbd/strsplit.h>
|
|
|
|
#include <ardour/audioengine.h>
|
|
#include <ardour/configuration.h>
|
|
#include <ardour/session.h>
|
|
#include <ardour/audio_diskstream.h>
|
|
#include <ardour/utils.h>
|
|
#include <ardour/audioplaylist.h>
|
|
#include <ardour/audiofilesource.h>
|
|
#include <ardour/destructive_filesource.h>
|
|
#include <ardour/sndfile_helpers.h>
|
|
#include <ardour/auditioner.h>
|
|
#include <ardour/export.h>
|
|
#include <ardour/redirect.h>
|
|
#include <ardour/send.h>
|
|
#include <ardour/insert.h>
|
|
#include <ardour/connection.h>
|
|
#include <ardour/slave.h>
|
|
#include <ardour/tempo.h>
|
|
#include <ardour/audio_track.h>
|
|
#include <ardour/cycle_timer.h>
|
|
#include <ardour/utils.h>
|
|
#include <ardour/named_selection.h>
|
|
#include <ardour/version.h>
|
|
#include <ardour/location.h>
|
|
#include <ardour/audioregion.h>
|
|
#include <ardour/crossfade.h>
|
|
#include <ardour/control_protocol_manager.h>
|
|
|
|
#include "i18n.h"
|
|
#include <locale.h>
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
|
|
void
|
|
Session::first_stage_init (string fullpath, string snapshot_name)
|
|
{
|
|
if (fullpath.length() == 0) {
|
|
throw failed_constructor();
|
|
}
|
|
|
|
char buf[PATH_MAX+1];
|
|
if (!realpath(fullpath.c_str(), buf) && (errno != ENOENT)) {
|
|
error << string_compose(_("Could not use path %1 (%s)"), buf, strerror(errno)) << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
_path = string(buf);
|
|
|
|
if (_path[_path.length()-1] != '/') {
|
|
_path += '/';
|
|
}
|
|
|
|
/* these two are just provisional settings. set_state()
|
|
will likely override them.
|
|
*/
|
|
|
|
_name = _current_snapshot_name = snapshot_name;
|
|
setup_raid_path (_path);
|
|
|
|
_current_frame_rate = _engine.frame_rate ();
|
|
_tempo_map = new TempoMap (_current_frame_rate);
|
|
_tempo_map->StateChanged.connect (mem_fun (*this, &Session::tempo_map_changed));
|
|
|
|
g_atomic_int_set (&processing_prohibited, 0);
|
|
send_cnt = 0;
|
|
insert_cnt = 0;
|
|
_transport_speed = 0;
|
|
_last_transport_speed = 0;
|
|
transport_sub_state = 0;
|
|
_transport_frame = 0;
|
|
last_stop_frame = 0;
|
|
end_location = new Location (0, 0, _("end"), Location::Flags ((Location::IsMark|Location::IsEnd)));
|
|
start_location = new Location (0, 0, _("start"), Location::Flags ((Location::IsMark|Location::IsStart)));
|
|
_end_location_is_free = true;
|
|
g_atomic_int_set (&_record_status, Disabled);
|
|
auto_play = false;
|
|
punch_in = false;
|
|
punch_out = false;
|
|
auto_loop = false;
|
|
seamless_loop = false;
|
|
loop_changing = false;
|
|
auto_input = true;
|
|
crossfades_active = false;
|
|
all_safe = false;
|
|
auto_return = false;
|
|
_last_roll_location = 0;
|
|
_last_record_location = 0;
|
|
pending_locate_frame = 0;
|
|
pending_locate_roll = false;
|
|
pending_locate_flush = false;
|
|
dstream_buffer_size = 0;
|
|
state_tree = 0;
|
|
state_was_pending = false;
|
|
set_next_event ();
|
|
outbound_mtc_smpte_frame = 0;
|
|
next_quarter_frame_to_send = -1;
|
|
current_block_size = 0;
|
|
_solo_latched = true;
|
|
_solo_model = InverseMute;
|
|
solo_update_disabled = false;
|
|
currently_soloing = false;
|
|
_have_captured = false;
|
|
_worst_output_latency = 0;
|
|
_worst_input_latency = 0;
|
|
_worst_track_latency = 0;
|
|
_state_of_the_state = StateOfTheState(CannotSave|InitialConnecting|Loading);
|
|
_slave = 0;
|
|
_slave_type = None;
|
|
butler_mixdown_buffer = 0;
|
|
butler_gain_buffer = 0;
|
|
auditioner = 0;
|
|
mmc_control = false;
|
|
midi_control = true;
|
|
mmc = 0;
|
|
post_transport_work = PostTransportWork (0);
|
|
g_atomic_int_set (&butler_should_do_transport_work, 0);
|
|
g_atomic_int_set (&butler_active, 0);
|
|
g_atomic_int_set (&_playback_load, 100);
|
|
g_atomic_int_set (&_capture_load, 100);
|
|
g_atomic_int_set (&_playback_load_min, 100);
|
|
g_atomic_int_set (&_capture_load_min, 100);
|
|
pending_audition_region = 0;
|
|
_edit_mode = Slide;
|
|
pending_edit_mode = _edit_mode;
|
|
_play_range = false;
|
|
_control_out = 0;
|
|
_master_out = 0;
|
|
input_auto_connect = AutoConnectOption (0);
|
|
output_auto_connect = AutoConnectOption (0);
|
|
waiting_to_start = false;
|
|
_exporting = false;
|
|
_gain_automation_buffer = 0;
|
|
_pan_automation_buffer = 0;
|
|
_npan_buffers = 0;
|
|
pending_abort = false;
|
|
layer_model = MoveAddHigher;
|
|
xfade_model = ShortCrossfade;
|
|
destructive_index = 0;
|
|
|
|
/* allocate conversion buffers */
|
|
_conversion_buffers[ButlerContext] = new char[AudioDiskstream::disk_io_frames() * 4];
|
|
_conversion_buffers[TransportContext] = new char[AudioDiskstream::disk_io_frames() * 4];
|
|
|
|
/* default short fade = 15ms */
|
|
|
|
Crossfade::set_short_xfade_length ((jack_nframes_t) floor ((15.0 * frame_rate()) / 1000.0));
|
|
DestructiveFileSource::setup_standard_crossfades (frame_rate());
|
|
|
|
last_mmc_step.tv_sec = 0;
|
|
last_mmc_step.tv_usec = 0;
|
|
step_speed = 0.0;
|
|
|
|
preroll.type = AnyTime::Frames;
|
|
preroll.frames = 0;
|
|
postroll.type = AnyTime::Frames;
|
|
postroll.frames = 0;
|
|
|
|
/* click sounds are unset by default, which causes us to internal
|
|
waveforms for clicks.
|
|
*/
|
|
|
|
_click_io = 0;
|
|
_clicking = false;
|
|
click_requested = false;
|
|
click_data = 0;
|
|
click_emphasis_data = 0;
|
|
click_length = 0;
|
|
click_emphasis_length = 0;
|
|
|
|
process_function = &Session::process_with_events;
|
|
|
|
last_smpte_when = 0;
|
|
_smpte_offset = 0;
|
|
_smpte_offset_negative = true;
|
|
last_smpte_valid = false;
|
|
|
|
last_rr_session_dir = session_dirs.begin();
|
|
refresh_disk_space ();
|
|
|
|
// set_default_fade (0.2, 5.0); /* steepness, millisecs */
|
|
|
|
/* default configuration */
|
|
|
|
do_not_record_plugins = false;
|
|
over_length_short = 2;
|
|
over_length_long = 10;
|
|
send_midi_timecode = false;
|
|
send_midi_machine_control = false;
|
|
shuttle_speed_factor = 1.0;
|
|
shuttle_speed_threshold = 5;
|
|
rf_speed = 2.0;
|
|
_meter_hold = 100; // XXX unknown units: number of calls to meter::set()
|
|
_meter_falloff = 1.5f; // XXX unknown units: refresh_rate
|
|
max_level = 0;
|
|
min_level = 0;
|
|
|
|
/* slave stuff */
|
|
|
|
average_slave_delta = 1800;
|
|
have_first_delta_accumulator = false;
|
|
delta_accumulator_cnt = 0;
|
|
slave_state = Stopped;
|
|
|
|
/* default SMPTE type is 30 FPS, non-drop */
|
|
|
|
set_smpte_type (30.0, false);
|
|
|
|
_engine.GraphReordered.connect (mem_fun (*this, &Session::graph_reordered));
|
|
|
|
/* These are all static "per-class" signals */
|
|
|
|
Region::CheckNewRegion.connect (mem_fun (*this, &Session::add_region));
|
|
AudioSource::AudioSourceCreated.connect (mem_fun (*this, &Session::add_audio_source));
|
|
Playlist::PlaylistCreated.connect (mem_fun (*this, &Session::add_playlist));
|
|
Redirect::RedirectCreated.connect (mem_fun (*this, &Session::add_redirect));
|
|
AudioDiskstream::AudioDiskstreamCreated.connect (mem_fun (*this, &Session::add_diskstream));
|
|
NamedSelection::NamedSelectionCreated.connect (mem_fun (*this, &Session::add_named_selection));
|
|
|
|
IO::MoreOutputs.connect (mem_fun (*this, &Session::ensure_passthru_buffers));
|
|
|
|
/* stop IO objects from doing stuff until we're ready for them */
|
|
|
|
IO::disable_panners ();
|
|
IO::disable_ports ();
|
|
IO::disable_connecting ();
|
|
}
|
|
|
|
int
|
|
Session::second_stage_init (bool new_session)
|
|
{
|
|
AudioFileSource::set_peak_dir (peak_dir());
|
|
|
|
if (!new_session) {
|
|
if (load_state (_current_snapshot_name)) {
|
|
return -1;
|
|
}
|
|
remove_empty_sounds ();
|
|
}
|
|
|
|
if (start_butler_thread()) {
|
|
return -1;
|
|
}
|
|
|
|
if (start_midi_thread ()) {
|
|
return -1;
|
|
}
|
|
|
|
if (state_tree) {
|
|
if (set_state (*state_tree->root())) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* we can't save till after ::when_engine_running() is called,
|
|
because otherwise we save state with no connections made.
|
|
therefore, we reset _state_of_the_state because ::set_state()
|
|
will have cleared it.
|
|
|
|
we also have to include Loading so that any events that get
|
|
generated between here and the end of ::when_engine_running()
|
|
will be processed directly rather than queued.
|
|
*/
|
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave|Loading);
|
|
|
|
// set_auto_input (true);
|
|
_locations.changed.connect (mem_fun (this, &Session::locations_changed));
|
|
_locations.added.connect (mem_fun (this, &Session::locations_added));
|
|
setup_click_sounds (0);
|
|
setup_midi_control ();
|
|
|
|
/* Pay attention ... */
|
|
|
|
_engine.Halted.connect (mem_fun (*this, &Session::engine_halted));
|
|
_engine.Xrun.connect (mem_fun (*this, &Session::xrun_recovery));
|
|
|
|
if (_engine.running()) {
|
|
when_engine_running();
|
|
} else {
|
|
first_time_running = _engine.Running.connect (mem_fun (*this, &Session::when_engine_running));
|
|
}
|
|
|
|
send_full_time_code ();
|
|
_engine.transport_locate (0);
|
|
deliver_mmc (MIDI::MachineControl::cmdMmcReset, 0);
|
|
deliver_mmc (MIDI::MachineControl::cmdLocate, 0);
|
|
|
|
ControlProtocolManager::instance().set_session (*this);
|
|
|
|
if (new_session) {
|
|
_end_location_is_free = true;
|
|
} else {
|
|
_end_location_is_free = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
string
|
|
Session::raid_path () const
|
|
{
|
|
string path;
|
|
|
|
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
|
path += (*i).path;
|
|
path += ':';
|
|
}
|
|
|
|
return path.substr (0, path.length() - 1); // drop final colon
|
|
}
|
|
|
|
void
|
|
Session::set_raid_path (string path)
|
|
{
|
|
/* public-access to setup_raid_path() */
|
|
|
|
setup_raid_path (path);
|
|
}
|
|
|
|
void
|
|
Session::setup_raid_path (string path)
|
|
{
|
|
string::size_type colon;
|
|
string remaining;
|
|
space_and_path sp;
|
|
string fspath;
|
|
string::size_type len = path.length();
|
|
int colons;
|
|
|
|
colons = 0;
|
|
|
|
if (path.length() == 0) {
|
|
return;
|
|
}
|
|
|
|
session_dirs.clear ();
|
|
|
|
for (string::size_type n = 0; n < len; ++n) {
|
|
if (path[n] == ':') {
|
|
colons++;
|
|
}
|
|
}
|
|
|
|
if (colons == 0) {
|
|
|
|
/* no multiple search path, just one location (common case) */
|
|
|
|
sp.path = path;
|
|
sp.blocks = 0;
|
|
session_dirs.push_back (sp);
|
|
|
|
string fspath;
|
|
|
|
/* sounds dir */
|
|
|
|
fspath += sp.path;
|
|
if (fspath[fspath.length()-1] != '/') {
|
|
fspath += '/';
|
|
}
|
|
fspath += sound_dir_name;
|
|
fspath += ':';
|
|
|
|
/* tape dir */
|
|
|
|
fspath += sp.path;
|
|
if (fspath[fspath.length()-1] != '/') {
|
|
fspath += '/';
|
|
}
|
|
fspath += tape_dir_name;
|
|
|
|
AudioFileSource::set_search_path (fspath);
|
|
|
|
return;
|
|
}
|
|
|
|
remaining = path;
|
|
|
|
while ((colon = remaining.find_first_of (':')) != string::npos) {
|
|
|
|
sp.blocks = 0;
|
|
sp.path = remaining.substr (0, colon);
|
|
session_dirs.push_back (sp);
|
|
|
|
/* add sounds to file search path */
|
|
|
|
fspath += sp.path;
|
|
if (fspath[fspath.length()-1] != '/') {
|
|
fspath += '/';
|
|
}
|
|
fspath += sound_dir_name;
|
|
fspath += ':';
|
|
|
|
/* add tape dir to file search path */
|
|
|
|
fspath += sp.path;
|
|
if (fspath[fspath.length()-1] != '/') {
|
|
fspath += '/';
|
|
}
|
|
fspath += tape_dir_name;
|
|
fspath += ':';
|
|
|
|
remaining = remaining.substr (colon+1);
|
|
}
|
|
|
|
if (remaining.length()) {
|
|
|
|
sp.blocks = 0;
|
|
sp.path = remaining;
|
|
|
|
fspath += ':';
|
|
fspath += sp.path;
|
|
if (fspath[fspath.length()-1] != '/') {
|
|
fspath += '/';
|
|
}
|
|
fspath += sound_dir_name;
|
|
fspath += ':';
|
|
|
|
fspath += sp.path;
|
|
if (fspath[fspath.length()-1] != '/') {
|
|
fspath += '/';
|
|
}
|
|
fspath += tape_dir_name;
|
|
|
|
session_dirs.push_back (sp);
|
|
}
|
|
|
|
/* set the AudioFileSource search path */
|
|
|
|
AudioFileSource::set_search_path (fspath);
|
|
|
|
/* reset the round-robin soundfile path thingie */
|
|
|
|
last_rr_session_dir = session_dirs.begin();
|
|
}
|
|
|
|
int
|
|
Session::create (bool& new_session, string* mix_template, jack_nframes_t initial_length)
|
|
{
|
|
string dir;
|
|
|
|
if (mkdir (_path.c_str(), 0755) < 0) {
|
|
if (errno == EEXIST) {
|
|
new_session = false;
|
|
} else {
|
|
error << string_compose(_("Session: cannot create session dir \"%1\" (%2)"), _path, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
} else {
|
|
new_session = true;
|
|
}
|
|
|
|
dir = peak_dir ();
|
|
|
|
if (mkdir (dir.c_str(), 0755) < 0) {
|
|
if (errno != EEXIST) {
|
|
error << string_compose(_("Session: cannot create session peakfile dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
dir = sound_dir ();
|
|
|
|
if (mkdir (dir.c_str(), 0755) < 0) {
|
|
if (errno != EEXIST) {
|
|
error << string_compose(_("Session: cannot create session sounds dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
dir = tape_dir ();
|
|
|
|
if (mkdir (dir.c_str(), 0755) < 0) {
|
|
if (errno != EEXIST) {
|
|
error << string_compose(_("Session: cannot create session tape dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
dir = dead_sound_dir ();
|
|
|
|
if (mkdir (dir.c_str(), 0755) < 0) {
|
|
if (errno != EEXIST) {
|
|
error << string_compose(_("Session: cannot create session dead sounds dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
dir = automation_dir ();
|
|
|
|
if (mkdir (dir.c_str(), 0755) < 0) {
|
|
if (errno != EEXIST) {
|
|
error << string_compose(_("Session: cannot create session automation dir \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
/* check new_session so we don't overwrite an existing one */
|
|
|
|
if (mix_template) {
|
|
if (new_session){
|
|
std::string in_path = *mix_template;
|
|
|
|
ifstream in(in_path.c_str());
|
|
|
|
if (in){
|
|
string out_path = _path;
|
|
out_path += _name;
|
|
out_path += _statefile_suffix;
|
|
|
|
ofstream out(out_path.c_str());
|
|
|
|
if (out){
|
|
out << in.rdbuf();
|
|
|
|
// okay, session is set up. Treat like normal saved
|
|
// session from now on.
|
|
|
|
new_session = false;
|
|
return 0;
|
|
|
|
} else {
|
|
error << string_compose (_("Could not open %1 for writing mix template"), out_path)
|
|
<< endmsg;
|
|
return -1;
|
|
}
|
|
|
|
} else {
|
|
error << string_compose (_("Could not open mix template %1 for reading"), in_path)
|
|
<< endmsg;
|
|
return -1;
|
|
}
|
|
|
|
|
|
} else {
|
|
warning << _("Session already exists. Not overwriting") << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (new_session) {
|
|
|
|
/* set initial start + end point */
|
|
|
|
start_location->set_end (0);
|
|
_locations.add (start_location);
|
|
|
|
end_location->set_end (initial_length);
|
|
_locations.add (end_location);
|
|
|
|
_state_of_the_state = Clean;
|
|
|
|
if (save_state (_current_snapshot_name)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Session::load_diskstreams (const XMLNode& node)
|
|
{
|
|
XMLNodeList clist;
|
|
XMLNodeConstIterator citer;
|
|
|
|
clist = node.children();
|
|
|
|
for (citer = clist.begin(); citer != clist.end(); ++citer) {
|
|
|
|
AudioDiskstream* dstream;
|
|
|
|
try {
|
|
dstream = new AudioDiskstream (*this, **citer);
|
|
/* added automatically by AudioDiskstreamCreated handler */
|
|
}
|
|
|
|
catch (failed_constructor& err) {
|
|
error << _("Session: could not load diskstream via XML state") << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Session::remove_pending_capture_state ()
|
|
{
|
|
string xml_path;
|
|
|
|
xml_path = _path;
|
|
xml_path += _current_snapshot_name;
|
|
xml_path += _pending_suffix;
|
|
|
|
unlink (xml_path.c_str());
|
|
}
|
|
|
|
int
|
|
Session::save_state (string snapshot_name, bool pending)
|
|
{
|
|
XMLTree tree;
|
|
string xml_path;
|
|
string bak_path;
|
|
|
|
if (_state_of_the_state & CannotSave) {
|
|
return 1;
|
|
}
|
|
|
|
tree.set_root (&get_state());
|
|
|
|
if (snapshot_name.empty()) {
|
|
snapshot_name = _current_snapshot_name;
|
|
}
|
|
|
|
if (!pending) {
|
|
|
|
xml_path = _path;
|
|
xml_path += snapshot_name;
|
|
xml_path += _statefile_suffix;
|
|
bak_path = xml_path;
|
|
bak_path += ".bak";
|
|
|
|
// Make backup of state file
|
|
|
|
if ((access (xml_path.c_str(), F_OK) == 0) &&
|
|
(rename(xml_path.c_str(), bak_path.c_str()))) {
|
|
error << _("could not backup old state file, current state not saved.") << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
} else {
|
|
|
|
xml_path = _path;
|
|
xml_path += snapshot_name;
|
|
xml_path += _pending_suffix;
|
|
|
|
}
|
|
|
|
if (!tree.write (xml_path)) {
|
|
error << string_compose (_("state could not be saved to %1"), xml_path) << endmsg;
|
|
|
|
/* don't leave a corrupt file lying around if it is
|
|
possible to fix.
|
|
*/
|
|
|
|
if (unlink (xml_path.c_str())) {
|
|
error << string_compose (_("could not remove corrupt state file %1"), xml_path) << endmsg;
|
|
} else {
|
|
if (!pending) {
|
|
if (rename (bak_path.c_str(), xml_path.c_str())) {
|
|
error << string_compose (_("could not restore state file from backup %1"), bak_path) << endmsg;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (!pending) {
|
|
|
|
bool was_dirty = dirty();
|
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty);
|
|
|
|
if (was_dirty) {
|
|
DirtyChanged (); /* EMIT SIGNAL */
|
|
}
|
|
|
|
StateSaved (snapshot_name); /* EMIT SIGNAL */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Session::restore_state (string snapshot_name)
|
|
{
|
|
if (load_state (snapshot_name) == 0) {
|
|
set_state (*state_tree->root());
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Session::load_state (string snapshot_name)
|
|
{
|
|
if (state_tree) {
|
|
delete state_tree;
|
|
state_tree = 0;
|
|
}
|
|
|
|
string xmlpath;
|
|
|
|
state_was_pending = false;
|
|
|
|
/* check for leftover pending state from a crashed capture attempt */
|
|
|
|
xmlpath = _path;
|
|
xmlpath += snapshot_name;
|
|
xmlpath += _pending_suffix;
|
|
|
|
if (!access (xmlpath.c_str(), F_OK)) {
|
|
|
|
/* there is pending state from a crashed capture attempt */
|
|
|
|
if (AskAboutPendingState()) {
|
|
state_was_pending = true;
|
|
}
|
|
}
|
|
|
|
if (!state_was_pending) {
|
|
|
|
xmlpath = _path;
|
|
xmlpath += snapshot_name;
|
|
xmlpath += _statefile_suffix;
|
|
}
|
|
|
|
if (access (xmlpath.c_str(), F_OK)) {
|
|
error << string_compose(_("%1: session state information file \"%2\" doesn't exist!"), _name, xmlpath) << endmsg;
|
|
return 1;
|
|
}
|
|
|
|
state_tree = new XMLTree;
|
|
|
|
set_dirty();
|
|
|
|
if (state_tree->read (xmlpath)) {
|
|
return 0;
|
|
} else {
|
|
error << string_compose(_("Could not understand ardour file %1"), xmlpath) << endmsg;
|
|
}
|
|
|
|
delete state_tree;
|
|
state_tree = 0;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
Session::load_options (const XMLNode& node)
|
|
{
|
|
XMLNode* child;
|
|
XMLProperty* prop;
|
|
bool have_fade_msecs = false;
|
|
bool have_fade_steepness = false;
|
|
float fade_msecs = 0;
|
|
float fade_steepness = 0;
|
|
SlaveSource slave_src = None;
|
|
int x;
|
|
LocaleGuard lg (X_("POSIX"));
|
|
|
|
if ((child = find_named_node (node, "input-auto-connect")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
sscanf (prop->value().c_str(), "%x", &x);
|
|
input_auto_connect = AutoConnectOption (x);
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "output-auto-connect")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
sscanf (prop->value().c_str(), "%x", &x);
|
|
output_auto_connect = AutoConnectOption (x);
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "slave")) != 0) {
|
|
if ((prop = child->property ("type")) != 0) {
|
|
if (prop->value() == "none") {
|
|
slave_src = None;
|
|
} else if (prop->value() == "mtc") {
|
|
slave_src = MTC;
|
|
} else if (prop->value() == "jack") {
|
|
slave_src = JACK;
|
|
}
|
|
set_slave_source (slave_src, 0);
|
|
}
|
|
}
|
|
|
|
/* we cannot set edit mode if we are loading a session,
|
|
because it might destroy the playlist's positioning
|
|
*/
|
|
|
|
if ((child = find_named_node (node, "edit-mode")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
if (prop->value() == "slide") {
|
|
pending_edit_mode = Slide;
|
|
} else if (prop->value() == "splice") {
|
|
pending_edit_mode = Splice;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "send-midi-timecode")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
bool x = (prop->value() == "yes");
|
|
send_mtc = !x; /* force change in value */
|
|
set_send_mtc (x);
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "send-midi-machine-control")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
bool x = (prop->value() == "yes");
|
|
send_mmc = !x; /* force change in value */
|
|
set_send_mmc (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "max-level")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
max_level = atoi (prop->value().c_str());
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "min-level")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
min_level = atoi (prop->value().c_str());
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "meter-hold")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
_meter_hold = atof (prop->value().c_str());
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "meter-falloff")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
_meter_falloff = atof (prop->value().c_str());
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "long-over-length")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
over_length_long = atoi (prop->value().c_str());
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "short-over-length")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
over_length_short = atoi (prop->value().c_str());
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "shuttle-speed-factor")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
shuttle_speed_factor = atof (prop->value().c_str());
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "shuttle-speed-threshold")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
shuttle_speed_threshold = atof (prop->value().c_str());
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "rf-speed")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
rf_speed = atof (prop->value().c_str());
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "smpte-frames-per-second")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_smpte_type( atof (prop->value().c_str()), smpte_drop_frames );
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "smpte-drop-frames")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_smpte_type( smpte_frames_per_second, (prop->value() == "yes") );
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "smpte-offset")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_smpte_offset( atoi (prop->value().c_str()) );
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "smpte-offset-negative")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_smpte_offset_negative( (prop->value() == "yes") );
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "click-sound")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
click_sound = prop->value();
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "click-emphasis-sound")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
click_emphasis_sound = prop->value();
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "solo-model")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
if (prop->value() == "SoloBus")
|
|
_solo_model = SoloBus;
|
|
else
|
|
_solo_model = InverseMute;
|
|
}
|
|
}
|
|
|
|
/* BOOLEAN OPTIONS */
|
|
|
|
if ((child = find_named_node (node, "auto-play")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_auto_play (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "auto-input")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_auto_input (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "seamless-loop")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_seamless_loop (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "punch-in")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_punch_in (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "punch-out")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_punch_out (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "auto-return")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_auto_return (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "send-mtc")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_send_mtc (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "mmc-control")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_mmc_control (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "midi-control")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_midi_control (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "midi-feedback")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_midi_feedback (prop->value() == "yes");
|
|
}
|
|
}
|
|
// Legacy support for <recording-plugins>
|
|
if ((child = find_named_node (node, "recording-plugins")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_do_not_record_plugins (prop->value() == "no");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "do-not-record-plugins")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_do_not_record_plugins (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "crossfades-active")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_crossfades_active (prop->value() == "yes");
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "audible-click")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
set_clicking (prop->value() == "yes");
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "end-marker-is-free")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
_end_location_is_free = (prop->value() == "yes");
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "layer-model")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
if (prop->value() == X_("LaterHigher")) {
|
|
set_layer_model (LaterHigher);
|
|
} else if (prop->value() == X_("AddHigher")) {
|
|
set_layer_model (AddHigher);
|
|
} else {
|
|
set_layer_model (MoveAddHigher);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "xfade-model")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
if (prop->value() == X_("Short")) {
|
|
set_xfade_model (ShortCrossfade);
|
|
} else {
|
|
set_xfade_model (FullCrossfade);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "short-xfade-length")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
/* value is stored as a fractional seconds */
|
|
float secs = atof (prop->value().c_str());
|
|
Crossfade::set_short_xfade_length ((jack_nframes_t) floor (secs * frame_rate()));
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "full-xfades-unmuted")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
crossfades_active = (prop->value() == "yes");
|
|
}
|
|
}
|
|
|
|
/* TIED OPTIONS */
|
|
|
|
if ((child = find_named_node (node, "default-fade-steepness")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
fade_steepness = atof (prop->value().c_str());
|
|
have_fade_steepness = true;
|
|
}
|
|
}
|
|
if ((child = find_named_node (node, "default-fade-msec")) != 0) {
|
|
if ((prop = child->property ("val")) != 0) {
|
|
fade_msecs = atof (prop->value().c_str());
|
|
have_fade_msecs = true;
|
|
}
|
|
}
|
|
|
|
if (have_fade_steepness || have_fade_msecs) {
|
|
// set_default_fade (fade_steepness, fade_msecs);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
XMLNode&
|
|
Session::get_options () const
|
|
{
|
|
XMLNode* opthead;
|
|
XMLNode* child;
|
|
char buf[32];
|
|
LocaleGuard lg (X_("POSIX"));
|
|
|
|
opthead = new XMLNode ("Options");
|
|
|
|
SlaveSource src = slave_source ();
|
|
string src_string;
|
|
switch (src) {
|
|
case None:
|
|
src_string = "none";
|
|
break;
|
|
case MTC:
|
|
src_string = "mtc";
|
|
break;
|
|
case JACK:
|
|
src_string = "jack";
|
|
break;
|
|
}
|
|
child = opthead->add_child ("slave");
|
|
child->add_property ("type", src_string);
|
|
|
|
child = opthead->add_child ("send-midi-timecode");
|
|
child->add_property ("val", send_midi_timecode?"yes":"no");
|
|
|
|
child = opthead->add_child ("send-midi-machine-control");
|
|
child->add_property ("val", send_midi_machine_control?"yes":"no");
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%x", (int) input_auto_connect);
|
|
child = opthead->add_child ("input-auto-connect");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%x", (int) output_auto_connect);
|
|
child = opthead->add_child ("output-auto-connect");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%d", max_level);
|
|
child = opthead->add_child ("max-level");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%d", min_level);
|
|
child = opthead->add_child ("min-level");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%f", _meter_hold);
|
|
child = opthead->add_child ("meter-hold");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%f", _meter_falloff);
|
|
child = opthead->add_child ("meter-falloff");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%u", over_length_long);
|
|
child = opthead->add_child ("long-over-length");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%u", over_length_short);
|
|
child = opthead->add_child ("short-over-length");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%f", shuttle_speed_factor);
|
|
child = opthead->add_child ("shuttle-speed-factor");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%f", shuttle_speed_threshold);
|
|
child = opthead->add_child ("shuttle-speed-threshold");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%f", rf_speed);
|
|
child = opthead->add_child ("rf-speed");
|
|
child->add_property ("val", buf);
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%.2f", smpte_frames_per_second);
|
|
child = opthead->add_child ("smpte-frames-per-second");
|
|
child->add_property ("val", buf);
|
|
|
|
child = opthead->add_child ("smpte-drop-frames");
|
|
child->add_property ("val", smpte_drop_frames ? "yes" : "no");
|
|
|
|
snprintf (buf, sizeof(buf)-1, "%u", smpte_offset ());
|
|
child = opthead->add_child ("smpte-offset");
|
|
child->add_property ("val", buf);
|
|
|
|
child = opthead->add_child ("smpte-offset-negative");
|
|
child->add_property ("val", smpte_offset_negative () ? "yes" : "no");
|
|
|
|
child = opthead->add_child ("edit-mode");
|
|
switch (_edit_mode) {
|
|
case Splice:
|
|
child->add_property ("val", "splice");
|
|
break;
|
|
|
|
case Slide:
|
|
child->add_property ("val", "slide");
|
|
break;
|
|
}
|
|
|
|
child = opthead->add_child ("auto-play");
|
|
child->add_property ("val", get_auto_play () ? "yes" : "no");
|
|
child = opthead->add_child ("auto-input");
|
|
child->add_property ("val", get_auto_input () ? "yes" : "no");
|
|
child = opthead->add_child ("seamless-loop");
|
|
child->add_property ("val", get_seamless_loop () ? "yes" : "no");
|
|
child = opthead->add_child ("punch-in");
|
|
child->add_property ("val", get_punch_in () ? "yes" : "no");
|
|
child = opthead->add_child ("punch-out");
|
|
child->add_property ("val", get_punch_out () ? "yes" : "no");
|
|
child = opthead->add_child ("all-safe");
|
|
child->add_property ("val", get_all_safe () ? "yes" : "no");
|
|
child = opthead->add_child ("auto-return");
|
|
child->add_property ("val", get_auto_return () ? "yes" : "no");
|
|
child = opthead->add_child ("mmc-control");
|
|
child->add_property ("val", get_mmc_control () ? "yes" : "no");
|
|
child = opthead->add_child ("midi-control");
|
|
child->add_property ("val", get_midi_control () ? "yes" : "no");
|
|
child = opthead->add_child ("midi-feedback");
|
|
child->add_property ("val", get_midi_feedback () ? "yes" : "no");
|
|
child = opthead->add_child ("do-not-record-plugins");
|
|
child->add_property ("val", get_do_not_record_plugins () ? "yes" : "no");
|
|
child = opthead->add_child ("auto-crossfade");
|
|
child->add_property ("val", get_crossfades_active () ? "yes" : "no");
|
|
child = opthead->add_child ("audible-click");
|
|
child->add_property ("val", get_clicking () ? "yes" : "no");
|
|
child = opthead->add_child ("end-marker-is-free");
|
|
child->add_property ("val", _end_location_is_free ? "yes" : "no");
|
|
|
|
if (click_sound.length()) {
|
|
child = opthead->add_child ("click-sound");
|
|
child->add_property ("val", click_sound);
|
|
}
|
|
|
|
if (click_emphasis_sound.length()) {
|
|
child = opthead->add_child ("click-emphasis-sound");
|
|
child->add_property ("val", click_emphasis_sound);
|
|
}
|
|
|
|
child = opthead->add_child ("solo-model");
|
|
child->add_property ("val", _solo_model == SoloBus ? "SoloBus" : "InverseMute");
|
|
|
|
child = opthead->add_child ("layer-model");
|
|
switch (layer_model) {
|
|
case LaterHigher:
|
|
child->add_property ("val", X_("LaterHigher"));
|
|
break;
|
|
case MoveAddHigher:
|
|
child->add_property ("val", X_("MoveAddHigher"));
|
|
break;
|
|
case AddHigher:
|
|
child->add_property ("val", X_("AddHigher"));
|
|
break;
|
|
}
|
|
|
|
child = opthead->add_child ("xfade-model");
|
|
switch (xfade_model) {
|
|
case FullCrossfade:
|
|
child->add_property ("val", X_("Full"));
|
|
break;
|
|
case ShortCrossfade:
|
|
child->add_property ("val", X_("Short"));
|
|
}
|
|
|
|
child = opthead->add_child ("short-xfade-length");
|
|
/* store as fractions of a second */
|
|
snprintf (buf, sizeof(buf)-1, "%f",
|
|
(float) Crossfade::short_xfade_length() / frame_rate());
|
|
child->add_property ("val", buf);
|
|
|
|
child = opthead->add_child ("full-xfades-unmuted");
|
|
child->add_property ("val", crossfades_active ? "yes" : "no");
|
|
|
|
return *opthead;
|
|
}
|
|
|
|
XMLNode&
|
|
Session::get_state()
|
|
{
|
|
return state(true);
|
|
}
|
|
|
|
XMLNode&
|
|
Session::get_template()
|
|
{
|
|
/* if we don't disable rec-enable, diskstreams
|
|
will believe they need to store their capture
|
|
sources in their state node.
|
|
*/
|
|
|
|
disable_record (false);
|
|
|
|
return state(false);
|
|
}
|
|
|
|
XMLNode&
|
|
Session::state(bool full_state)
|
|
{
|
|
XMLNode* node = new XMLNode("Session");
|
|
XMLNode* child;
|
|
|
|
// store libardour version, just in case
|
|
char buf[16];
|
|
snprintf(buf, sizeof(buf)-1, "%d.%d.%d",
|
|
libardour_major_version, libardour_minor_version, libardour_micro_version);
|
|
node->add_property("version", string(buf));
|
|
|
|
/* store configuration settings */
|
|
|
|
if (full_state) {
|
|
|
|
/* store the name */
|
|
node->add_property ("name", _name);
|
|
|
|
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()) {
|
|
p += ':';
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
++next;
|
|
++i;
|
|
}
|
|
|
|
child = node->add_child ("Path");
|
|
child->add_content (p);
|
|
}
|
|
}
|
|
|
|
node->add_child_nocopy (get_options());
|
|
|
|
child = node->add_child ("Sources");
|
|
|
|
if (full_state) {
|
|
Glib::Mutex::Lock sl (audio_source_lock);
|
|
|
|
for (AudioSourceList::iterator siter = audio_sources.begin(); siter != audio_sources.end(); ++siter) {
|
|
|
|
/* Don't save information about AudioFileSources that are empty */
|
|
|
|
AudioFileSource* fs;
|
|
|
|
if ((fs = dynamic_cast<AudioFileSource*> ((*siter).second)) != 0) {
|
|
DestructiveFileSource* dfs = dynamic_cast<DestructiveFileSource*> (fs);
|
|
|
|
/* destructive file sources are OK if they are empty, because
|
|
we will re-use them every time.
|
|
*/
|
|
|
|
if (!dfs) {
|
|
if (fs->length() == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
child->add_child_nocopy ((*siter).second->get_state());
|
|
}
|
|
}
|
|
|
|
child = node->add_child ("Regions");
|
|
|
|
if (full_state) {
|
|
Glib::Mutex::Lock rl (region_lock);
|
|
|
|
for (AudioRegionList::const_iterator i = audio_regions.begin(); i != audio_regions.end(); ++i) {
|
|
|
|
/* only store regions not attached to playlists */
|
|
|
|
if ((*i).second->playlist() == 0) {
|
|
child->add_child_nocopy (i->second->state (true));
|
|
}
|
|
}
|
|
}
|
|
|
|
child = node->add_child ("DiskStreams");
|
|
|
|
{
|
|
Glib::RWLock::ReaderLock dl (diskstream_lock);
|
|
for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) {
|
|
if (!(*i)->hidden()) {
|
|
child->add_child_nocopy ((*i)->get_state());
|
|
}
|
|
}
|
|
}
|
|
|
|
node->add_child_nocopy (_locations.get_state());
|
|
|
|
child = node->add_child ("Connections");
|
|
{
|
|
Glib::Mutex::Lock lm (connection_lock);
|
|
for (ConnectionList::iterator i = _connections.begin(); i != _connections.end(); ++i) {
|
|
if (!(*i)->system_dependent()) {
|
|
child->add_child_nocopy ((*i)->get_state());
|
|
}
|
|
}
|
|
}
|
|
|
|
child = node->add_child ("Routes");
|
|
{
|
|
Glib::RWLock::ReaderLock lm (route_lock);
|
|
|
|
RoutePublicOrderSorter cmp;
|
|
RouteList public_order(routes);
|
|
public_order.sort (cmp);
|
|
|
|
for (RouteList::iterator i = public_order.begin(); i != public_order.end(); ++i) {
|
|
if (!(*i)->hidden()) {
|
|
if (full_state) {
|
|
child->add_child_nocopy ((*i)->get_state());
|
|
} else {
|
|
child->add_child_nocopy ((*i)->get_template());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
child = node->add_child ("EditGroups");
|
|
for (list<RouteGroup *>::iterator i = edit_groups.begin(); i != edit_groups.end(); ++i) {
|
|
child->add_child_nocopy ((*i)->get_state());
|
|
}
|
|
|
|
child = node->add_child ("MixGroups");
|
|
for (list<RouteGroup *>::iterator i = mix_groups.begin(); i != mix_groups.end(); ++i) {
|
|
child->add_child_nocopy ((*i)->get_state());
|
|
}
|
|
|
|
child = node->add_child ("Playlists");
|
|
for (PlaylistList::iterator i = playlists.begin(); i != playlists.end(); ++i) {
|
|
if (!(*i)->hidden()) {
|
|
if (!(*i)->empty()) {
|
|
if (full_state) {
|
|
child->add_child_nocopy ((*i)->get_state());
|
|
} else {
|
|
child->add_child_nocopy ((*i)->get_template());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
child = node->add_child ("UnusedPlaylists");
|
|
for (PlaylistList::iterator i = unused_playlists.begin(); i != unused_playlists.end(); ++i) {
|
|
if (!(*i)->hidden()) {
|
|
if (!(*i)->empty()) {
|
|
if (full_state) {
|
|
child->add_child_nocopy ((*i)->get_state());
|
|
} else {
|
|
child->add_child_nocopy ((*i)->get_template());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (_click_io) {
|
|
child = node->add_child ("Click");
|
|
child->add_child_nocopy (_click_io->state (full_state));
|
|
}
|
|
|
|
if (full_state) {
|
|
child = node->add_child ("NamedSelections");
|
|
for (NamedSelectionList::iterator i = named_selections.begin(); i != named_selections.end(); ++i) {
|
|
if (full_state) {
|
|
child->add_child_nocopy ((*i)->get_state());
|
|
}
|
|
}
|
|
}
|
|
|
|
node->add_child_nocopy (_tempo_map->get_state());
|
|
|
|
if (_extra_xml) {
|
|
node->add_child_copy (*_extra_xml);
|
|
}
|
|
|
|
return *node;
|
|
}
|
|
|
|
int
|
|
Session::set_state (const XMLNode& node)
|
|
{
|
|
XMLNodeList nlist;
|
|
XMLNode* child;
|
|
const XMLProperty* prop;
|
|
int ret = -1;
|
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave);
|
|
|
|
if (node.name() != "Session"){
|
|
fatal << _("programming error: Session: incorrect XML node sent to set_state()") << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
StateManager::prohibit_save ();
|
|
|
|
if ((prop = node.property ("name")) != 0) {
|
|
_name = prop->value ();
|
|
}
|
|
|
|
IO::disable_ports ();
|
|
IO::disable_connecting ();
|
|
|
|
/* Object loading order:
|
|
|
|
MIDI
|
|
Path
|
|
extra
|
|
Options
|
|
Sources
|
|
AudioRegions
|
|
AudioDiskstreams
|
|
Connections
|
|
Locations
|
|
Routes
|
|
EditGroups
|
|
MixGroups
|
|
Click
|
|
*/
|
|
|
|
if (use_config_midi_ports ()) {
|
|
}
|
|
|
|
if ((child = find_named_node (node, "Path")) != 0) {
|
|
/* XXX this XML content stuff horrible API design */
|
|
string raid_path = _path + ':' + child->children().front()->content();
|
|
setup_raid_path (raid_path);
|
|
} else {
|
|
/* the path is already set */
|
|
}
|
|
|
|
if ((child = find_named_node (node, "extra")) != 0) {
|
|
_extra_xml = new XMLNode (*child);
|
|
}
|
|
|
|
if ((child = find_named_node (node, "Options")) == 0) {
|
|
error << _("Session: XML state has no options section") << endmsg;
|
|
} else if (load_options (*child)) {
|
|
}
|
|
|
|
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, "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;
|
|
} else if (load_playlists (*child)) {
|
|
goto out;
|
|
}
|
|
|
|
if ((child = find_named_node (node, "UnusedPlaylists")) == 0) {
|
|
// this is OK
|
|
} else if (load_unused_playlists (*child)) {
|
|
goto out;
|
|
}
|
|
|
|
if ((child = find_named_node (node, "NamedSelections")) != 0) {
|
|
if (load_named_selections (*child)) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if ((child = find_named_node (node, "DiskStreams")) == 0) {
|
|
error << _("Session: XML state has no diskstreams section") << endmsg;
|
|
goto out;
|
|
} else if (load_diskstreams (*child)) {
|
|
goto out;
|
|
}
|
|
|
|
if ((child = find_named_node (node, "Connections")) == 0) {
|
|
error << _("Session: XML state has no connections section") << endmsg;
|
|
goto out;
|
|
} else if (load_connections (*child)) {
|
|
goto out;
|
|
}
|
|
|
|
if ((child = find_named_node (node, "Locations")) == 0) {
|
|
error << _("Session: XML state has no locations section") << endmsg;
|
|
goto out;
|
|
} else if (_locations.set_state (*child)) {
|
|
goto out;
|
|
}
|
|
|
|
Location* location;
|
|
|
|
if ((location = _locations.auto_loop_location()) != 0) {
|
|
set_auto_loop_location (location);
|
|
}
|
|
|
|
if ((location = _locations.auto_punch_location()) != 0) {
|
|
set_auto_punch_location (location);
|
|
}
|
|
|
|
if ((location = _locations.end_location()) == 0) {
|
|
_locations.add (end_location);
|
|
} else {
|
|
delete end_location;
|
|
end_location = location;
|
|
}
|
|
|
|
if ((location = _locations.start_location()) == 0) {
|
|
_locations.add (start_location);
|
|
} else {
|
|
delete start_location;
|
|
start_location = location;
|
|
}
|
|
|
|
_locations.save_state (_("initial state"));
|
|
|
|
if ((child = find_named_node (node, "EditGroups")) == 0) {
|
|
error << _("Session: XML state has no edit groups section") << endmsg;
|
|
goto out;
|
|
} else if (load_edit_groups (*child)) {
|
|
goto out;
|
|
}
|
|
|
|
if ((child = find_named_node (node, "MixGroups")) == 0) {
|
|
error << _("Session: XML state has no mix groups section") << endmsg;
|
|
goto out;
|
|
} else if (load_mix_groups (*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)) {
|
|
goto out;
|
|
}
|
|
|
|
if ((child = find_named_node (node, "Routes")) == 0) {
|
|
error << _("Session: XML state has no routes section") << endmsg;
|
|
goto out;
|
|
} else if (load_routes (*child)) {
|
|
goto out;
|
|
}
|
|
|
|
if ((child = find_named_node (node, "Click")) == 0) {
|
|
warning << _("Session: XML state has no click section") << endmsg;
|
|
} else if (_click_io) {
|
|
_click_io->set_state (*child);
|
|
}
|
|
|
|
/* OK, now we can set edit mode */
|
|
|
|
set_edit_mode (pending_edit_mode);
|
|
|
|
/* here beginneth the second phase ... */
|
|
|
|
StateReady (); /* EMIT SIGNAL */
|
|
|
|
_state_of_the_state = Clean;
|
|
|
|
StateManager::allow_save (_("initial state"), true);
|
|
|
|
if (state_was_pending) {
|
|
save_state (_current_snapshot_name);
|
|
remove_pending_capture_state ();
|
|
state_was_pending = false;
|
|
}
|
|
|
|
return 0;
|
|
|
|
out:
|
|
/* we failed, re-enable state saving but don't actually save internal state */
|
|
StateManager::allow_save (X_("ignored"), false);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
Session::load_routes (const XMLNode& node)
|
|
{
|
|
XMLNodeList nlist;
|
|
XMLNodeConstIterator niter;
|
|
Route *route;
|
|
|
|
nlist = node.children();
|
|
|
|
set_dirty();
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((route = XMLRouteFactory (**niter)) == 0) {
|
|
error << _("Session: cannot create Route from XML description.") << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
add_route (route);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Route *
|
|
Session::XMLRouteFactory (const XMLNode& node)
|
|
{
|
|
if (node.name() != "Route") {
|
|
return 0;
|
|
}
|
|
|
|
if (node.property ("diskstream") != 0 || node.property ("diskstream-id") != 0) {
|
|
return new AudioTrack (*this, node);
|
|
} else {
|
|
return new Route (*this, node);
|
|
}
|
|
}
|
|
|
|
int
|
|
Session::load_regions (const XMLNode& node)
|
|
{
|
|
XMLNodeList nlist;
|
|
XMLNodeConstIterator niter;
|
|
AudioRegion* 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.") << endmsg;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
AudioRegion *
|
|
Session::XMLRegionFactory (const XMLNode& node, bool full)
|
|
{
|
|
const XMLProperty* prop;
|
|
id_t s_id;
|
|
Source* source;
|
|
AudioSource* as;
|
|
AudioRegion::SourceList sources;
|
|
uint32_t nchans = 1;
|
|
char buf[128];
|
|
|
|
if (node.name() != X_("Region")) {
|
|
return 0;
|
|
}
|
|
|
|
if ((prop = node.property (X_("channels"))) != 0) {
|
|
nchans = atoi (prop->value().c_str());
|
|
}
|
|
|
|
|
|
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 0;
|
|
}
|
|
}
|
|
|
|
sscanf (prop->value().c_str(), "%" PRIu64, &s_id);
|
|
|
|
if ((source = get_source (s_id)) == 0) {
|
|
error << string_compose(_("Session: XMLNode describing a AudioRegion references an unknown source id =%1"), s_id) << endmsg;
|
|
return 0;
|
|
}
|
|
|
|
as = dynamic_cast<AudioSource*>(source);
|
|
if (!as) {
|
|
error << string_compose(_("Session: XMLNode describing a AudioRegion references a non-audio source id =%1"), s_id) << endmsg;
|
|
return 0;
|
|
}
|
|
|
|
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) {
|
|
sscanf (prop->value().c_str(), "%" PRIu64, &s_id);
|
|
|
|
if ((source = get_source (s_id)) == 0) {
|
|
error << string_compose(_("Session: XMLNode describing a AudioRegion references an unknown source id =%1"), s_id) << endmsg;
|
|
return 0;
|
|
}
|
|
|
|
as = dynamic_cast<AudioSource*>(source);
|
|
if (!as) {
|
|
error << string_compose(_("Session: XMLNode describing a AudioRegion references a non-audio source id =%1"), s_id) << endmsg;
|
|
return 0;
|
|
}
|
|
sources.push_back (as);
|
|
}
|
|
}
|
|
|
|
|
|
try {
|
|
return new AudioRegion (sources, node);
|
|
}
|
|
|
|
catch (failed_constructor& err) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
XMLNode&
|
|
Session::get_sources_as_xml ()
|
|
|
|
{
|
|
XMLNode* node = new XMLNode (X_("Sources"));
|
|
Glib::Mutex::Lock lm (audio_source_lock);
|
|
|
|
for (AudioSourceList::iterator i = audio_sources.begin(); i != audio_sources.end(); ++i) {
|
|
node->add_child_nocopy ((*i).second->get_state());
|
|
}
|
|
|
|
/* XXX get MIDI and other sources here */
|
|
|
|
return *node;
|
|
}
|
|
|
|
string
|
|
Session::path_from_region_name (string name, string identifier)
|
|
{
|
|
char buf[PATH_MAX+1];
|
|
uint32_t n;
|
|
string dir = discover_best_sound_dir ();
|
|
|
|
for (n = 0; n < 999999; ++n) {
|
|
if (identifier.length()) {
|
|
snprintf (buf, sizeof(buf), "%s/%s%s%" PRIu32 ".wav", dir.c_str(), name.c_str(),
|
|
identifier.c_str(), n);
|
|
} else {
|
|
snprintf (buf, sizeof(buf), "%s/%s-%" PRIu32 ".wav", dir.c_str(), name.c_str(), n);
|
|
}
|
|
if (access (buf, F_OK) != 0) {
|
|
return buf;
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
int
|
|
Session::load_sources (const XMLNode& node)
|
|
{
|
|
XMLNodeList nlist;
|
|
XMLNodeConstIterator niter;
|
|
Source* source;
|
|
|
|
nlist = node.children();
|
|
|
|
set_dirty();
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((source = XMLSourceFactory (**niter)) == 0) {
|
|
error << _("Session: cannot create Source from XML description.") << endmsg;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Source *
|
|
Session::XMLSourceFactory (const XMLNode& node)
|
|
{
|
|
Source *src = 0;
|
|
|
|
if (node.name() != "Source") {
|
|
return 0;
|
|
}
|
|
|
|
try {
|
|
src = AudioFileSource::create (node);
|
|
}
|
|
|
|
catch (failed_constructor& err) {
|
|
error << _("Found a sound file that cannot be used by Ardour. Talk to the progammers.") << endmsg;
|
|
return 0;
|
|
}
|
|
|
|
return src;
|
|
}
|
|
|
|
int
|
|
Session::save_template (string template_name)
|
|
{
|
|
XMLTree tree;
|
|
string xml_path, bak_path, template_path;
|
|
|
|
if (_state_of_the_state & CannotSave) {
|
|
return -1;
|
|
}
|
|
|
|
DIR* dp;
|
|
string dir = template_dir();
|
|
|
|
if ((dp = opendir (dir.c_str()))) {
|
|
closedir (dp);
|
|
} else {
|
|
if (mkdir (dir.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)<0) {
|
|
error << string_compose(_("Could not create mix templates directory \"%1\" (%2)"), dir, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
tree.set_root (&get_template());
|
|
|
|
xml_path = dir;
|
|
xml_path += template_name;
|
|
xml_path += _template_suffix;
|
|
|
|
ifstream in(xml_path.c_str());
|
|
|
|
if (in) {
|
|
warning << string_compose(_("Template \"%1\" already exists - new version not created"), template_name) << endmsg;
|
|
return -1;
|
|
} else {
|
|
in.close();
|
|
}
|
|
|
|
if (!tree.write (xml_path)) {
|
|
error << _("mix template not saved") << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Session::rename_template (string old_name, string new_name)
|
|
{
|
|
string old_path = template_dir() + old_name + _template_suffix;
|
|
string new_path = template_dir() + new_name + _template_suffix;
|
|
|
|
return rename (old_path.c_str(), new_path.c_str());
|
|
}
|
|
|
|
int
|
|
Session::delete_template (string name)
|
|
{
|
|
string template_path = template_dir();
|
|
template_path += name;
|
|
template_path += _template_suffix;
|
|
|
|
return remove (template_path.c_str());
|
|
}
|
|
|
|
void
|
|
Session::refresh_disk_space ()
|
|
{
|
|
#if HAVE_SYS_VFS_H
|
|
struct statfs statfsbuf;
|
|
vector<space_and_path>::iterator i;
|
|
Glib::Mutex::Lock lm (space_lock);
|
|
double scale;
|
|
|
|
/* get freespace on every FS that is part of the session path */
|
|
|
|
_total_free_4k_blocks = 0;
|
|
|
|
for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
|
statfs ((*i).path.c_str(), &statfsbuf);
|
|
|
|
scale = statfsbuf.f_bsize/4096.0;
|
|
|
|
(*i).blocks = (uint32_t) floor (statfsbuf.f_bavail * scale);
|
|
_total_free_4k_blocks += (*i).blocks;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int
|
|
Session::ensure_sound_dir (string path, string& result)
|
|
{
|
|
string dead;
|
|
string peak;
|
|
|
|
/* Ensure that the parent directory exists */
|
|
|
|
if (mkdir (path.c_str(), 0775)) {
|
|
if (errno != EEXIST) {
|
|
error << string_compose(_("cannot create session directory \"%1\"; ignored"), path) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Ensure that the sounds directory exists */
|
|
|
|
result = path;
|
|
result += '/';
|
|
result += sound_dir_name;
|
|
|
|
if (mkdir (result.c_str(), 0775)) {
|
|
if (errno != EEXIST) {
|
|
error << string_compose(_("cannot create sounds directory \"%1\"; ignored"), result) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
dead = path;
|
|
dead += '/';
|
|
dead += dead_sound_dir_name;
|
|
|
|
if (mkdir (dead.c_str(), 0775)) {
|
|
if (errno != EEXIST) {
|
|
error << string_compose(_("cannot create dead sounds directory \"%1\"; ignored"), dead) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
peak = path;
|
|
peak += '/';
|
|
peak += peak_dir_name;
|
|
|
|
if (mkdir (peak.c_str(), 0775)) {
|
|
if (errno != EEXIST) {
|
|
error << string_compose(_("cannot create peak file directory \"%1\"; ignored"), peak) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* callers expect this to be terminated ... */
|
|
|
|
result += '/';
|
|
return 0;
|
|
}
|
|
|
|
string
|
|
Session::discover_best_sound_dir (bool destructive)
|
|
{
|
|
vector<space_and_path>::iterator i;
|
|
string result;
|
|
|
|
/* destructive files all go into the same place */
|
|
|
|
if (destructive) {
|
|
return tape_dir();
|
|
}
|
|
|
|
/* handle common case without system calls */
|
|
|
|
if (session_dirs.size() == 1) {
|
|
return sound_dir();
|
|
}
|
|
|
|
/* OK, here's the algorithm we're following here:
|
|
|
|
We want to select which directory to use for
|
|
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
|
|
much space available, we use RR selection between them.
|
|
If not, then we pick the filesystem with the most space.
|
|
|
|
This gets a good balance between the two
|
|
approaches.
|
|
*/
|
|
|
|
refresh_disk_space ();
|
|
|
|
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) {
|
|
|
|
bool found_it = false;
|
|
|
|
/* 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()) {
|
|
if (ensure_sound_dir ((*i).path, result) == 0) {
|
|
last_rr_session_dir = i;
|
|
found_it = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} while (i != last_rr_session_dir);
|
|
|
|
if (!found_it) {
|
|
result = sound_dir();
|
|
}
|
|
|
|
} else {
|
|
|
|
/* pick FS with the most freespace (and that
|
|
seems to actually work ...)
|
|
*/
|
|
|
|
vector<space_and_path> sorted;
|
|
space_and_path_ascending_cmp cmp;
|
|
|
|
sorted = session_dirs;
|
|
sort (sorted.begin(), sorted.end(), cmp);
|
|
|
|
for (i = sorted.begin(); i != sorted.end(); ++i) {
|
|
if (ensure_sound_dir ((*i).path, result) == 0) {
|
|
last_rr_session_dir = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if the above fails, fall back to the most simplistic solution */
|
|
|
|
if (i == sorted.end()) {
|
|
return sound_dir();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
Session::load_playlists (const XMLNode& node)
|
|
{
|
|
XMLNodeList nlist;
|
|
XMLNodeConstIterator niter;
|
|
Playlist *playlist;
|
|
|
|
nlist = node.children();
|
|
|
|
set_dirty();
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((playlist = XMLPlaylistFactory (**niter)) == 0) {
|
|
error << _("Session: cannot create Playlist from XML description.") << endmsg;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Session::load_unused_playlists (const XMLNode& node)
|
|
{
|
|
XMLNodeList nlist;
|
|
XMLNodeConstIterator niter;
|
|
Playlist *playlist;
|
|
|
|
nlist = node.children();
|
|
|
|
set_dirty();
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((playlist = XMLPlaylistFactory (**niter)) == 0) {
|
|
error << _("Session: cannot create Playlist from XML description.") << endmsg;
|
|
continue;
|
|
}
|
|
|
|
// now manually untrack it
|
|
|
|
track_playlist (playlist, false);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
Playlist *
|
|
Session::XMLPlaylistFactory (const XMLNode& node)
|
|
{
|
|
try {
|
|
return new AudioPlaylist (*this, node);
|
|
}
|
|
|
|
catch (failed_constructor& err) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int
|
|
Session::load_named_selections (const XMLNode& node)
|
|
{
|
|
XMLNodeList nlist;
|
|
XMLNodeConstIterator niter;
|
|
NamedSelection *ns;
|
|
|
|
nlist = node.children();
|
|
|
|
set_dirty();
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
|
|
if ((ns = XMLNamedSelectionFactory (**niter)) == 0) {
|
|
error << _("Session: cannot create Named Selection from XML description.") << endmsg;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
NamedSelection *
|
|
Session::XMLNamedSelectionFactory (const XMLNode& node)
|
|
{
|
|
try {
|
|
return new NamedSelection (*this, node);
|
|
}
|
|
|
|
catch (failed_constructor& err) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
string
|
|
Session::dead_sound_dir () const
|
|
{
|
|
string res = _path;
|
|
res += dead_sound_dir_name;
|
|
res += '/';
|
|
return res;
|
|
}
|
|
|
|
string
|
|
Session::sound_dir () const
|
|
{
|
|
string res = _path;
|
|
res += sound_dir_name;
|
|
res += '/';
|
|
return res;
|
|
}
|
|
|
|
string
|
|
Session::tape_dir () const
|
|
{
|
|
string res = _path;
|
|
res += tape_dir_name;
|
|
res += '/';
|
|
return res;
|
|
}
|
|
|
|
string
|
|
Session::peak_dir () const
|
|
{
|
|
string res = _path;
|
|
res += peak_dir_name;
|
|
res += '/';
|
|
return res;
|
|
}
|
|
|
|
string
|
|
Session::automation_dir () const
|
|
{
|
|
string res = _path;
|
|
res += "automation/";
|
|
return res;
|
|
}
|
|
|
|
string
|
|
Session::template_dir ()
|
|
{
|
|
string path = get_user_ardour_path();
|
|
path += "templates/";
|
|
|
|
return path;
|
|
}
|
|
|
|
string
|
|
Session::suffixed_search_path (string suffix, bool data)
|
|
{
|
|
string path;
|
|
|
|
path += get_user_ardour_path();
|
|
if (path[path.length()-1] != ':') {
|
|
path += ':';
|
|
}
|
|
|
|
if (data) {
|
|
path += get_system_data_path();
|
|
} else {
|
|
path += get_system_module_path();
|
|
}
|
|
|
|
vector<string> split_path;
|
|
|
|
split (path, split_path, ':');
|
|
path = "";
|
|
|
|
for (vector<string>::iterator i = split_path.begin(); i != split_path.end(); ++i) {
|
|
path += *i;
|
|
path += suffix;
|
|
path += '/';
|
|
|
|
if (distance (i, split_path.end()) != 1) {
|
|
path += ':';
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
string
|
|
Session::template_path ()
|
|
{
|
|
return suffixed_search_path (X_("templates"), true);
|
|
}
|
|
|
|
string
|
|
Session::control_protocol_path ()
|
|
{
|
|
return suffixed_search_path (X_("surfaces"), false);
|
|
}
|
|
|
|
int
|
|
Session::load_connections (const XMLNode& node)
|
|
{
|
|
XMLNodeList nlist = node.children();
|
|
XMLNodeConstIterator niter;
|
|
|
|
set_dirty();
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
if ((*niter)->name() == "InputConnection") {
|
|
add_connection (new ARDOUR::InputConnection (**niter));
|
|
} else if ((*niter)->name() == "OutputConnection") {
|
|
add_connection (new ARDOUR::OutputConnection (**niter));
|
|
} else {
|
|
error << string_compose(_("Unknown node \"%1\" found in Connections list from state file"), (*niter)->name()) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Session::load_edit_groups (const XMLNode& node)
|
|
{
|
|
return load_route_groups (node, true);
|
|
}
|
|
|
|
int
|
|
Session::load_mix_groups (const XMLNode& node)
|
|
{
|
|
return load_route_groups (node, false);
|
|
}
|
|
|
|
int
|
|
Session::load_route_groups (const XMLNode& node, bool edit)
|
|
{
|
|
XMLNodeList nlist = node.children();
|
|
XMLNodeConstIterator niter;
|
|
RouteGroup* rg;
|
|
|
|
set_dirty();
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
if ((*niter)->name() == "RouteGroup") {
|
|
if (edit) {
|
|
rg = add_edit_group ("");
|
|
rg->set_state (**niter);
|
|
} else {
|
|
rg = add_mix_group ("");
|
|
rg->set_state (**niter);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Session::swap_configuration(Configuration** new_config)
|
|
{
|
|
Glib::RWLock::WriterLock lm (route_lock); // jlc - WHY?
|
|
Configuration* tmp = *new_config;
|
|
*new_config = Config;
|
|
Config = tmp;
|
|
set_dirty();
|
|
}
|
|
|
|
void
|
|
Session::copy_configuration(Configuration* new_config)
|
|
{
|
|
Glib::RWLock::WriterLock lm (route_lock);
|
|
new_config = new Configuration(*Config);
|
|
}
|
|
|
|
static bool
|
|
state_file_filter (const string &str, void *arg)
|
|
{
|
|
return (str.length() > strlen(Session::statefile_suffix()) &&
|
|
str.find (Session::statefile_suffix()) == (str.length() - strlen (Session::statefile_suffix())));
|
|
}
|
|
|
|
struct string_cmp {
|
|
bool operator()(const string* a, const string* b) {
|
|
return *a < *b;
|
|
}
|
|
};
|
|
|
|
static string*
|
|
remove_end(string* state)
|
|
{
|
|
string statename(*state);
|
|
|
|
string::size_type start,end;
|
|
if ((start = statename.find_last_of ('/')) != string::npos) {
|
|
statename = statename.substr (start+1);
|
|
}
|
|
|
|
if ((end = statename.rfind(".ardour")) < 0) {
|
|
end = statename.length();
|
|
}
|
|
|
|
return new string(statename.substr (0, end));
|
|
}
|
|
|
|
vector<string *> *
|
|
Session::possible_states (string path)
|
|
{
|
|
PathScanner scanner;
|
|
vector<string*>* states = scanner (path, state_file_filter, 0, false, false);
|
|
|
|
transform(states->begin(), states->end(), states->begin(), remove_end);
|
|
|
|
string_cmp cmp;
|
|
sort (states->begin(), states->end(), cmp);
|
|
|
|
return states;
|
|
}
|
|
|
|
vector<string *> *
|
|
Session::possible_states () const
|
|
{
|
|
return possible_states(_path);
|
|
}
|
|
|
|
void
|
|
Session::auto_save()
|
|
{
|
|
save_state (_current_snapshot_name);
|
|
}
|
|
|
|
RouteGroup *
|
|
Session::add_edit_group (string name)
|
|
{
|
|
RouteGroup* rg = new RouteGroup (*this, name);
|
|
edit_groups.push_back (rg);
|
|
edit_group_added (rg); /* EMIT SIGNAL */
|
|
set_dirty();
|
|
return rg;
|
|
}
|
|
|
|
RouteGroup *
|
|
Session::add_mix_group (string name)
|
|
{
|
|
RouteGroup* rg = new RouteGroup (*this, name, RouteGroup::Relative);
|
|
mix_groups.push_back (rg);
|
|
mix_group_added (rg); /* EMIT SIGNAL */
|
|
set_dirty();
|
|
return rg;
|
|
}
|
|
|
|
void
|
|
Session::remove_edit_group (RouteGroup& rg)
|
|
{
|
|
list<RouteGroup*>::iterator i;
|
|
|
|
if ((i = find (edit_groups.begin(), edit_groups.end(), &rg)) != edit_groups.end()) {
|
|
(*i)->apply (&Route::drop_edit_group, this);
|
|
edit_groups.erase (i);
|
|
edit_group_removed (); /* EMIT SIGNAL */
|
|
}
|
|
|
|
delete &rg;
|
|
}
|
|
|
|
void
|
|
Session::remove_mix_group (RouteGroup& rg)
|
|
{
|
|
list<RouteGroup*>::iterator i;
|
|
|
|
if ((i = find (mix_groups.begin(), mix_groups.end(), &rg)) != mix_groups.end()) {
|
|
(*i)->apply (&Route::drop_mix_group, this);
|
|
mix_groups.erase (i);
|
|
mix_group_removed (); /* EMIT SIGNAL */
|
|
}
|
|
|
|
delete &rg;
|
|
}
|
|
|
|
RouteGroup *
|
|
Session::mix_group_by_name (string name)
|
|
{
|
|
list<RouteGroup *>::iterator i;
|
|
|
|
for (i = mix_groups.begin(); i != mix_groups.end(); ++i) {
|
|
if ((*i)->name() == name) {
|
|
return* i;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
RouteGroup *
|
|
Session::edit_group_by_name (string name)
|
|
{
|
|
list<RouteGroup *>::iterator i;
|
|
|
|
for (i = edit_groups.begin(); i != edit_groups.end(); ++i) {
|
|
if ((*i)->name() == name) {
|
|
return* i;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Session::set_meter_hold (float val)
|
|
{
|
|
_meter_hold = val;
|
|
MeterHoldChanged(); // emit
|
|
}
|
|
|
|
void
|
|
Session::set_meter_falloff (float val)
|
|
{
|
|
_meter_falloff = val;
|
|
MeterFalloffChanged(); // emit
|
|
}
|
|
|
|
|
|
void
|
|
Session::begin_reversible_command (string name)
|
|
{
|
|
current_trans.clear ();
|
|
current_trans.set_name (name);
|
|
}
|
|
|
|
void
|
|
Session::commit_reversible_command (Command *cmd)
|
|
{
|
|
struct timeval now;
|
|
|
|
if (cmd) {
|
|
current_trans.add_command (*cmd);
|
|
}
|
|
|
|
gettimeofday (&now, 0);
|
|
current_trans.set_timestamp (now);
|
|
|
|
history.add (current_trans);
|
|
}
|
|
|
|
Session::GlobalRouteBooleanState
|
|
Session::get_global_route_boolean (bool (Route::*method)(void) const)
|
|
{
|
|
GlobalRouteBooleanState s;
|
|
Glib::RWLock::ReaderLock lm (route_lock);
|
|
|
|
for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
|
|
if (!(*i)->hidden()) {
|
|
RouteBooleanState v;
|
|
|
|
v.first =* i;
|
|
v.second = ((*i)->*method)();
|
|
|
|
s.push_back (v);
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
Session::GlobalRouteMeterState
|
|
Session::get_global_route_metering ()
|
|
{
|
|
GlobalRouteMeterState s;
|
|
Glib::RWLock::ReaderLock lm (route_lock);
|
|
|
|
for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
|
|
if (!(*i)->hidden()) {
|
|
RouteMeterState v;
|
|
|
|
v.first =* i;
|
|
v.second = (*i)->meter_point();
|
|
|
|
s.push_back (v);
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
void
|
|
Session::set_global_route_metering (GlobalRouteMeterState s, void* arg)
|
|
{
|
|
for (GlobalRouteMeterState::iterator i = s.begin(); i != s.end(); ++i) {
|
|
i->first->set_meter_point (i->second, arg);
|
|
}
|
|
}
|
|
|
|
void
|
|
Session::set_global_route_boolean (GlobalRouteBooleanState s, void (Route::*method)(bool, void*), void* arg)
|
|
{
|
|
for (GlobalRouteBooleanState::iterator i = s.begin(); i != s.end(); ++i) {
|
|
(i->first->*method) (i->second, arg);
|
|
}
|
|
}
|
|
|
|
void
|
|
Session::set_global_mute (GlobalRouteBooleanState s, void* src)
|
|
{
|
|
set_global_route_boolean (s, &Route::set_mute, src);
|
|
}
|
|
|
|
void
|
|
Session::set_global_solo (GlobalRouteBooleanState s, void* src)
|
|
{
|
|
set_global_route_boolean (s, &Route::set_solo, src);
|
|
}
|
|
|
|
void
|
|
Session::set_global_record_enable (GlobalRouteBooleanState s, void* src)
|
|
{
|
|
set_global_route_boolean (s, &Route::set_record_enable, src);
|
|
}
|
|
|
|
UndoAction
|
|
Session::global_mute_memento (void* src)
|
|
{
|
|
return sigc::bind (mem_fun (*this, &Session::set_global_mute), get_global_route_boolean (&Route::muted), src);
|
|
}
|
|
|
|
UndoAction
|
|
Session::global_metering_memento (void* src)
|
|
{
|
|
return sigc::bind (mem_fun (*this, &Session::set_global_route_metering), get_global_route_metering (), src);
|
|
}
|
|
|
|
UndoAction
|
|
Session::global_solo_memento (void* src)
|
|
{
|
|
return sigc::bind (mem_fun (*this, &Session::set_global_solo), get_global_route_boolean (&Route::soloed), src);
|
|
}
|
|
|
|
UndoAction
|
|
Session::global_record_enable_memento (void* src)
|
|
{
|
|
return sigc::bind (mem_fun (*this, &Session::set_global_record_enable), get_global_route_boolean (&Route::record_enabled), src);
|
|
}
|
|
|
|
static bool
|
|
template_filter (const string &str, void *arg)
|
|
{
|
|
return (str.length() > strlen(Session::template_suffix()) &&
|
|
str.find (Session::template_suffix()) == (str.length() - strlen (Session::template_suffix())));
|
|
}
|
|
|
|
void
|
|
Session::get_template_list (list<string> &template_names)
|
|
{
|
|
vector<string *> *templates;
|
|
PathScanner scanner;
|
|
string path;
|
|
|
|
path = template_path ();
|
|
|
|
templates = scanner (path, template_filter, 0, false, true);
|
|
|
|
vector<string*>::iterator i;
|
|
for (i = templates->begin(); i != templates->end(); ++i) {
|
|
string fullpath = *(*i);
|
|
int start, end;
|
|
|
|
start = fullpath.find_last_of ('/') + 1;
|
|
if ((end = fullpath.find_last_of ('.')) <0) {
|
|
end = fullpath.length();
|
|
}
|
|
|
|
template_names.push_back(fullpath.substr(start, (end-start)));
|
|
}
|
|
}
|
|
|
|
int
|
|
Session::read_favorite_dirs (FavoriteDirs & favs)
|
|
{
|
|
string path = get_user_ardour_path();
|
|
path += "/favorite_dirs";
|
|
|
|
ifstream fav (path.c_str());
|
|
|
|
favs.clear();
|
|
|
|
if (!fav) {
|
|
if (errno != ENOENT) {
|
|
//error << string_compose (_("cannot open favorite file %1 (%2)"), path, strerror (errno)) << endmsg;
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
|
|
string newfav;
|
|
|
|
getline(fav, newfav);
|
|
|
|
if (!fav.good()) {
|
|
break;
|
|
}
|
|
|
|
favs.push_back (newfav);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Session::write_favorite_dirs (FavoriteDirs & favs)
|
|
{
|
|
string path = get_user_ardour_path();
|
|
path += "/favorite_dirs";
|
|
|
|
ofstream fav (path.c_str());
|
|
|
|
if (!fav) {
|
|
return -1;
|
|
}
|
|
|
|
for (FavoriteDirs::iterator i = favs.begin(); i != favs.end(); ++i) {
|
|
fav << (*i) << endl;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool
|
|
accept_all_non_peak_files (const string& path, void *arg)
|
|
{
|
|
return (path.length() > 5 && path.find (".peak") != (path.length() - 5));
|
|
}
|
|
|
|
static bool
|
|
accept_all_state_files (const string& path, void *arg)
|
|
{
|
|
return (path.length() > 7 && path.find (".ardour") == (path.length() - 7));
|
|
}
|
|
|
|
int
|
|
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) {
|
|
|
|
XMLProperty* prop;
|
|
|
|
if ((prop = (*niter)->property (X_("name"))) == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (prop->value()[0] == '/') {
|
|
/* external file, ignore */
|
|
continue;
|
|
}
|
|
|
|
string path = _path; /* /-terminated */
|
|
path += sound_dir_name;
|
|
path += '/';
|
|
path += prop->value();
|
|
|
|
result.insert (path);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Session::find_all_sources_across_snapshots (set<string>& result, bool exclude_this_snapshot)
|
|
{
|
|
PathScanner scanner;
|
|
vector<string*>* state_files;
|
|
string ripped;
|
|
string this_snapshot_path;
|
|
|
|
result.clear ();
|
|
|
|
ripped = _path;
|
|
|
|
if (ripped[ripped.length()-1] == '/') {
|
|
ripped = ripped.substr (0, ripped.length() - 1);
|
|
}
|
|
|
|
state_files = scanner (ripped, accept_all_state_files, (void *) 0, false, true);
|
|
|
|
if (state_files == 0) {
|
|
/* impossible! */
|
|
return 0;
|
|
}
|
|
|
|
this_snapshot_path = _path;
|
|
this_snapshot_path += _current_snapshot_name;
|
|
this_snapshot_path += _statefile_suffix;
|
|
|
|
for (vector<string*>::iterator i = state_files->begin(); i != state_files->end(); ++i) {
|
|
|
|
if (exclude_this_snapshot && **i == this_snapshot_path) {
|
|
continue;
|
|
}
|
|
|
|
if (find_all_sources (**i, result) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
Session::cleanup_sources (Session::cleanup_report& rep)
|
|
{
|
|
vector<Source*> dead_sources;
|
|
vector<Playlist*> playlists_tbd;
|
|
PathScanner scanner;
|
|
string sound_path;
|
|
vector<space_and_path>::iterator i;
|
|
vector<space_and_path>::iterator nexti;
|
|
vector<string*>* soundfiles;
|
|
vector<string> unused;
|
|
set<string> all_sources;
|
|
bool used;
|
|
string spath;
|
|
int ret = -1;
|
|
|
|
_state_of_the_state = (StateOfTheState) (_state_of_the_state | InCleanup);
|
|
|
|
/* step 1: consider deleting all unused playlists */
|
|
|
|
for (PlaylistList::iterator x = unused_playlists.begin(); x != unused_playlists.end(); ++x) {
|
|
int status;
|
|
|
|
status = AskAboutPlaylistDeletion (*x);
|
|
|
|
switch (status) {
|
|
case -1:
|
|
ret = 0;
|
|
goto out;
|
|
break;
|
|
|
|
case 0:
|
|
playlists_tbd.push_back (*x);
|
|
break;
|
|
|
|
default:
|
|
/* leave it alone */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* now delete any that were marked for deletion */
|
|
|
|
for (vector<Playlist*>::iterator x = playlists_tbd.begin(); x != playlists_tbd.end(); ++x) {
|
|
PlaylistList::iterator foo;
|
|
|
|
if ((foo = unused_playlists.find (*x)) != unused_playlists.end()) {
|
|
unused_playlists.erase (foo);
|
|
}
|
|
delete *x;
|
|
}
|
|
|
|
/* step 2: clear the undo/redo history for all playlists */
|
|
|
|
for (PlaylistList::iterator x = playlists.begin(); x != playlists.end(); ++x) {
|
|
(*x)->drop_all_states ();
|
|
}
|
|
|
|
/* step 3: find all un-referenced sources */
|
|
|
|
rep.paths.clear ();
|
|
rep.space = 0;
|
|
|
|
for (AudioSourceList::iterator i = audio_sources.begin(); i != audio_sources.end(); ) {
|
|
|
|
AudioSourceList::iterator tmp;
|
|
|
|
tmp = i;
|
|
++tmp;
|
|
|
|
/* only remove files that are not in use and have some size
|
|
to them. otherwise we remove the current "nascent"
|
|
capture files.
|
|
*/
|
|
|
|
if ((*i).second->use_cnt() == 0 && (*i).second->length() > 0) {
|
|
dead_sources.push_back (i->second);
|
|
|
|
/* remove this source from our own list to avoid us
|
|
adding it to the list of all sources below
|
|
*/
|
|
|
|
audio_sources.erase (i);
|
|
}
|
|
|
|
i = tmp;
|
|
}
|
|
|
|
/* Step 4: get rid of all regions in the region list that use any dead sources
|
|
in case the sources themselves don't go away (they might be referenced in
|
|
other snapshots).
|
|
*/
|
|
|
|
for (vector<Source*>::iterator i = dead_sources.begin(); i != dead_sources.end();++i) {
|
|
|
|
for (AudioRegionList::iterator r = audio_regions.begin(); r != audio_regions.end(); ) {
|
|
AudioRegionList::iterator tmp;
|
|
AudioRegion* ar;
|
|
|
|
tmp = r;
|
|
++tmp;
|
|
|
|
ar = (*r).second;
|
|
|
|
for (uint32_t n = 0; n < ar->n_channels(); ++n) {
|
|
if (&ar->source (n) == (*i)) {
|
|
/* this region is dead */
|
|
remove_region (ar);
|
|
}
|
|
}
|
|
|
|
r = tmp;
|
|
}
|
|
}
|
|
|
|
/* build a list of all the possible sound directories for the session */
|
|
|
|
for (i = session_dirs.begin(); i != session_dirs.end(); ) {
|
|
|
|
nexti = i;
|
|
++nexti;
|
|
|
|
sound_path += (*i).path;
|
|
sound_path += sound_dir_name;
|
|
|
|
if (nexti != session_dirs.end()) {
|
|
sound_path += ':';
|
|
}
|
|
|
|
i = nexti;
|
|
}
|
|
|
|
/* now do the same thing for the files that ended up in the sounds dir(s)
|
|
but are not referenced as sources in any snapshot.
|
|
*/
|
|
|
|
soundfiles = scanner (sound_path, accept_all_non_peak_files, (void *) 0, false, true);
|
|
|
|
if (soundfiles == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* find all sources, but don't use this snapshot because the
|
|
state file on disk still references sources we may have already
|
|
dropped.
|
|
*/
|
|
|
|
find_all_sources_across_snapshots (all_sources, true);
|
|
|
|
/* add our current source list
|
|
*/
|
|
|
|
for (AudioSourceList::iterator i = audio_sources.begin(); i != audio_sources.end(); ++i) {
|
|
AudioFileSource* fs;
|
|
|
|
if ((fs = dynamic_cast<AudioFileSource*> ((*i).second)) != 0) {
|
|
all_sources.insert (fs->path());
|
|
}
|
|
}
|
|
|
|
for (vector<string*>::iterator x = soundfiles->begin(); x != soundfiles->end(); ++x) {
|
|
|
|
used = false;
|
|
spath = **x;
|
|
|
|
for (set<string>::iterator i = all_sources.begin(); i != all_sources.end(); ++i) {
|
|
|
|
if (spath == *i) {
|
|
used = true;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (!used) {
|
|
unused.push_back (spath);
|
|
}
|
|
}
|
|
|
|
/* now try to move all unused files into the "dead_sounds" directory(ies) */
|
|
|
|
for (vector<string>::iterator x = unused.begin(); x != unused.end(); ++x) {
|
|
struct stat statbuf;
|
|
|
|
rep.paths.push_back (*x);
|
|
if (stat ((*x).c_str(), &statbuf) == 0) {
|
|
rep.space += statbuf.st_size;
|
|
}
|
|
|
|
string newpath;
|
|
|
|
/* don't move the file across filesystems, just
|
|
stick it in the `dead_sound_dir_name' directory
|
|
on whichever filesystem it was already on.
|
|
*/
|
|
|
|
newpath = Glib::path_get_dirname (*x);
|
|
newpath = Glib::path_get_dirname (newpath);
|
|
|
|
newpath += '/';
|
|
newpath += dead_sound_dir_name;
|
|
newpath += '/';
|
|
newpath += Glib::path_get_basename ((*x));
|
|
|
|
if (access (newpath.c_str(), F_OK) == 0) {
|
|
|
|
/* the new path already exists, try versioning */
|
|
|
|
char buf[PATH_MAX+1];
|
|
int version = 1;
|
|
string newpath_v;
|
|
|
|
snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
|
|
newpath_v = buf;
|
|
|
|
while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
|
|
snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
|
|
newpath_v = buf;
|
|
}
|
|
|
|
if (version == 999) {
|
|
error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
|
|
newpath)
|
|
<< endmsg;
|
|
} else {
|
|
newpath = newpath_v;
|
|
}
|
|
|
|
} else {
|
|
|
|
/* it doesn't exist, or we can't read it or something */
|
|
|
|
}
|
|
|
|
if (::rename ((*x).c_str(), newpath.c_str()) != 0) {
|
|
error << string_compose (_("cannot rename audio file source from %1 to %2 (%3)"),
|
|
(*x), newpath, strerror (errno))
|
|
<< endmsg;
|
|
goto out;
|
|
}
|
|
|
|
|
|
/* see if there an easy to find peakfile for this file, and remove it.
|
|
*/
|
|
|
|
string peakpath = (*x).substr (0, (*x).find_last_of ('.'));
|
|
peakpath += ".peak";
|
|
|
|
if (access (peakpath.c_str(), W_OK) == 0) {
|
|
if (::unlink (peakpath.c_str()) != 0) {
|
|
error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
|
|
peakpath, _path, strerror (errno))
|
|
<< endmsg;
|
|
/* try to back out */
|
|
rename (newpath.c_str(), _path.c_str());
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
/* dump the history list */
|
|
|
|
history.clear ();
|
|
|
|
/* save state so we don't end up a session file
|
|
referring to non-existent sources.
|
|
*/
|
|
|
|
save_state ("");
|
|
|
|
out:
|
|
_state_of_the_state = (StateOfTheState) (_state_of_the_state & ~InCleanup);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
Session::cleanup_trash_sources (Session::cleanup_report& rep)
|
|
{
|
|
vector<space_and_path>::iterator i;
|
|
string dead_sound_dir;
|
|
struct dirent* dentry;
|
|
struct stat statbuf;
|
|
DIR* dead;
|
|
|
|
rep.paths.clear ();
|
|
rep.space = 0;
|
|
|
|
for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
|
|
|
dead_sound_dir = (*i).path;
|
|
dead_sound_dir += dead_sound_dir_name;
|
|
|
|
if ((dead = opendir (dead_sound_dir.c_str())) == 0) {
|
|
continue;
|
|
}
|
|
|
|
while ((dentry = readdir (dead)) != 0) {
|
|
|
|
/* avoid '.' and '..' */
|
|
|
|
if ((dentry->d_name[0] == '.' && dentry->d_name[1] == '\0') ||
|
|
(dentry->d_name[2] == '\0' && dentry->d_name[0] == '.' && dentry->d_name[1] == '.')) {
|
|
continue;
|
|
}
|
|
|
|
string fullpath;
|
|
|
|
fullpath = dead_sound_dir;
|
|
fullpath += '/';
|
|
fullpath += dentry->d_name;
|
|
|
|
if (stat (fullpath.c_str(), &statbuf)) {
|
|
continue;
|
|
}
|
|
|
|
if (!S_ISREG (statbuf.st_mode)) {
|
|
continue;
|
|
}
|
|
|
|
if (unlink (fullpath.c_str())) {
|
|
error << string_compose (_("cannot remove dead sound file %1 (%2)"),
|
|
fullpath, strerror (errno))
|
|
<< endmsg;
|
|
}
|
|
|
|
rep.paths.push_back (dentry->d_name);
|
|
rep.space += statbuf.st_size;
|
|
}
|
|
|
|
closedir (dead);
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Session::set_dirty ()
|
|
{
|
|
bool was_dirty = dirty();
|
|
|
|
_state_of_the_state = StateOfTheState (_state_of_the_state | Dirty);
|
|
|
|
if (!was_dirty) {
|
|
DirtyChanged(); /* EMIT SIGNAL */
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Session::set_clean ()
|
|
{
|
|
bool was_dirty = dirty();
|
|
|
|
_state_of_the_state = Clean;
|
|
|
|
if (was_dirty) {
|
|
DirtyChanged(); /* EMIT SIGNAL */
|
|
}
|
|
}
|
|
|