#include <boost/algorithm/string/erase.hpp>
-#include "pbd/error.h"
-#include "pbd/boost_debug.h"
-#include "pbd/pathscanner.h"
-#include "pbd/stl_delete.h"
#include "pbd/basename.h"
-#include "pbd/stacktrace.h"
-#include "pbd/file_utils.h"
+#include "pbd/boost_debug.h"
+#include "pbd/convert.h"
#include "pbd/convert.h"
-#include "pbd/strsplit.h"
+#include "pbd/error.h"
+#include "pbd/file_utils.h"
+#include "pbd/md5.h"
+#include "pbd/search_path.h"
+#include "pbd/stacktrace.h"
+#include "pbd/stl_delete.h"
#include "pbd/unwind.h"
#include "ardour/amp.h"
#include "ardour/filename_extensions.h"
#include "ardour/graph.h"
#include "ardour/midiport_manager.h"
+#include "ardour/scene_changer.h"
#include "ardour/midi_track.h"
#include "ardour/midi_ui.h"
#include "ardour/operations.h"
#include "ardour/region_factory.h"
#include "ardour/route_graph.h"
#include "ardour/route_group.h"
+#include "ardour/route_sorters.h"
#include "ardour/send.h"
#include "ardour/session.h"
#include "ardour/session_directory.h"
#include "ardour/smf_source.h"
#include "ardour/source_factory.h"
#include "ardour/speakers.h"
+#include "ardour/track.h"
#include "ardour/utils.h"
#include "midi++/port.h"
PBD::Signal0<void> Session::SuccessfulGraphSort;
PBD::Signal2<void,std::string,std::string> Session::VersionMismatch;
+const framecnt_t Session::bounce_chunk_size = 65536;
static void clean_up_session_event (SessionEvent* ev) { delete ev; }
const SessionEvent::RTeventCallback Session::rt_cleanup (clean_up_session_event);
: playlists (new SessionPlaylists)
, _engine (eng)
, process_function (&Session::process_with_events)
+ , _bounce_processing_active (false)
, waiting_for_sync_offset (false)
, _base_frame_rate (0)
, _current_frame_rate (0)
, state_tree (0)
, state_was_pending (false)
, _state_of_the_state (StateOfTheState(CannotSave|InitialConnecting|Loading))
+ , _suspend_save (0)
+ , _save_queued (false)
, _last_roll_location (0)
, _last_roll_or_reversal_location (0)
, _last_record_location (0)
, routes (new RouteList)
, _adding_routes_in_progress (false)
, destructive_index (0)
+ , _track_number_decimals(1)
, solo_update_disabled (false)
, default_fade_steepness (0)
, default_fade_msecs (0)
, _speakers (new Speakers)
, _order_hint (0)
, ignore_route_processor_changes (false)
+ , _scene_changer (0)
, _midi_ports (0)
, _mmc (0)
{
throw failed_constructor ();
}
+ /* load default session properties - if any */
+ config.load_state();
+
} else {
if (load_state (_current_snapshot_name)) {
clear_clicks ();
+ /* need to remove auditioner before monitoring section
+ * otherwise it is re-connected */
+ auditioner.reset ();
+
+ /* drop references to routes held by the monitoring section
+ * specifically _monitor_out aux/listen references */
+ remove_monitor_section();
+
/* clear out any pending dead wood from RCU managed objects */
routes.flush ();
/* reset these three references to special routes before we do the usual route delete thing */
- auditioner.reset ();
_master_out.reset ();
_monitor_out.reset ();
}
routes.flush ();
- DEBUG_TRACE (DEBUG::Destruction, "delete sources\n");
- for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
- DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for source %1 ; pre-ref = %2\n", i->second->name(), i->second.use_count()));
- i->second->drop_references ();
- }
+ {
+ DEBUG_TRACE (DEBUG::Destruction, "delete sources\n");
+ Glib::Threads::Mutex::Lock lm (source_lock);
+ for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+ DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for source %1 ; pre-ref = %2\n", i->second->name(), i->second.use_count()));
+ i->second->drop_references ();
+ }
- sources.clear ();
+ sources.clear ();
+ }
DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n");
for (list<RouteGroup *>::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) {
/* not strictly necessary, but doing it here allows the shared_ptr debugging to work */
playlists.reset ();
+ delete _scene_changer; _scene_changer = 0;
+
delete _mmc; _mmc = 0;
delete _midi_ports; _midi_ports = 0;
delete _locations; _locations = 0;
/* force reversion to Solo-In-Place */
Config->set_solo_control_is_listen_control (false);
+ /* if we are auditioning, cancel it ... this is a workaround
+ to a problem (auditioning does not execute the process graph,
+ which is needed to remove routes when using >1 core for processing)
+ */
+ cancel_audition ();
+
{
/* Hold process lock while doing this so that we don't hear bits and
* pieces of audio as we work on each route.
remove_route (_monitor_out);
auto_connect_master_bus ();
+
+ if (auditioner) {
+ auditioner->connect ();
+ }
}
void
(*x)->enable_monitor_send ();
}
}
+
+ if (auditioner) {
+ auditioner->connect ();
+ }
}
void
failed:
if (!new_routes.empty()) {
+ StateProtector sp (this);
add_routes (new_routes, true, true, true);
if (instrument) {
failed:
if (!new_routes.empty()) {
+ StateProtector sp (this);
add_routes (new_routes, true, true, true);
}
failure:
if (!ret.empty()) {
+ StateProtector sp (this);
add_routes (ret, false, true, true); // autoconnect outputs only
}
out:
if (!ret.empty()) {
+ StateProtector sp (this);
add_routes (ret, true, true, true);
IO::enable_connecting ();
}
save_state (_current_snapshot_name);
}
+ reassign_track_numbers();
+
RouteAdded (new_routes); /* EMIT SIGNAL */
}
}
}
+ /* if the monitoring section had a pointer to this route, remove it */
+ if (_monitor_out && !route->is_master() && !route->is_monitor()) {
+ Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
+ PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+ route->remove_aux_or_listen (_monitor_out);
+ }
+
boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (route);
if (mt && mt->step_editing()) {
if (_step_editors > 0) {
if (save_state (_current_snapshot_name)) {
save_history (_current_snapshot_name);
}
+ reassign_track_numbers();
}
void
return boost::shared_ptr<Route> ((Route*) 0);
}
+
+void
+Session::reassign_track_numbers ()
+{
+ int64_t tn = 0;
+ int64_t bn = 0;
+ RouteList r (*(routes.reader ()));
+ SignalOrderRouteSorter sorter;
+ r.sort (sorter);
+
+ StateProtector sp (this);
+
+ for (RouteList::iterator i = r.begin(); i != r.end(); ++i) {
+ if (boost::dynamic_pointer_cast<Track> (*i)) {
+ (*i)->set_track_number(++tn);
+ }
+ else if (!(*i)->is_master() && !(*i)->is_monitor() && !(*i)->is_auditioner()) {
+ (*i)->set_track_number(--bn);
+ }
+ }
+ const uint32_t decimals = ceilf (log10f (tn + 1));
+ const bool decimals_changed = _track_number_decimals != decimals;
+ _track_number_decimals = decimals;
+
+ if (decimals_changed && config.get_track_name_number ()) {
+ for (RouteList::iterator i = r.begin(); i != r.end(); ++i) {
+ boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (*i);
+ if (t) {
+ t->resync_track_name();
+ }
+ }
+ // trigger GUI re-layout
+ config.ParameterChanged("track-name-number");
+ }
+}
+
void
Session::playlist_region_added (boost::weak_ptr<Region> w)
{
}
}
- if (!(_state_of_the_state & InCleanup)) {
+ if (!(_state_of_the_state & StateOfTheState (InCleanup|Loading))) {
/* save state so we don't end up with a session file
referring to non-existent sources.
return source;
}
-boost::shared_ptr<Source>
-Session::source_by_path_and_channel (const string& path, uint16_t chn)
+boost::shared_ptr<AudioFileSource>
+Session::audio_source_by_path_and_channel (const string& path, uint16_t chn) const
{
+ /* Restricted to audio files because only audio sources have channel
+ as a property.
+ */
+
Glib::Threads::Mutex::Lock lm (source_lock);
- for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) {
+ for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
boost::shared_ptr<AudioFileSource> afs
= boost::dynamic_pointer_cast<AudioFileSource>(i->second);
return afs;
}
}
- return boost::shared_ptr<Source>();
+
+ return boost::shared_ptr<AudioFileSource>();
+}
+
+boost::shared_ptr<MidiSource>
+Session::midi_source_by_path (const std::string& path) const
+{
+ /* Restricted to MIDI files because audio sources require a channel
+ for unique identification, in addition to a path.
+ */
+
+ Glib::Threads::Mutex::Lock lm (source_lock);
+
+ for (SourceMap::const_iterator s = sources.begin(); s != sources.end(); ++s) {
+ boost::shared_ptr<MidiSource> ms
+ = boost::dynamic_pointer_cast<MidiSource>(s->second);
+ boost::shared_ptr<FileSource> fs
+ = boost::dynamic_pointer_cast<FileSource>(s->second);
+
+ if (ms && fs && fs->path() == path) {
+ return ms;
+ }
+ }
+
+ return boost::shared_ptr<MidiSource>();
}
uint32_t
return cnt;
}
-
string
-Session::change_source_path_by_name (string path, string oldname, string newname, bool destructive)
+Session::peak_path (string base) const
{
- string look_for;
- string old_basename = PBD::basename_nosuffix (oldname);
- string new_legalized = legalize_for_path (newname);
-
- /* note: we know (or assume) the old path is already valid */
-
- if (destructive) {
-
- /* destructive file sources have a name of the form:
-
- /path/to/Tnnnn-NAME(%[LR])?.wav
-
- the task here is to replace NAME with the new name.
- */
-
- string dir;
- string prefix;
- string::size_type dash;
-
- dir = Glib::path_get_dirname (path);
- path = Glib::path_get_basename (path);
-
- /* '-' is not a legal character for the NAME part of the path */
-
- if ((dash = path.find_last_of ('-')) == string::npos) {
- return "";
- }
-
- prefix = path.substr (0, dash);
-
- path += prefix;
- path += '-';
- path += new_legalized;
- path += native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
- path = Glib::build_filename (dir, path);
-
- } else {
-
- /* non-destructive file sources have a name of the form:
-
- /path/to/NAME-nnnnn(%[LR])?.ext
+ return Glib::build_filename (_session_dir->peak_path(), base + peakfile_suffix);
+}
- the task here is to replace NAME with the new name.
- */
+string
+Session::new_audio_source_path_for_embedded (const std::string& path)
+{
+ /* embedded source:
+ *
+ * we know that the filename is already unique because it exists
+ * out in the filesystem.
+ *
+ * However, when we bring it into the session, we could get a
+ * collision.
+ *
+ * Eg. two embedded files:
+ *
+ * /foo/bar/baz.wav
+ * /frob/nic/baz.wav
+ *
+ * When merged into session, these collide.
+ *
+ * There will not be a conflict with in-memory sources
+ * because when the source was created we already picked
+ * a unique name for it.
+ *
+ * This collision is not likely to be common, but we have to guard
+ * against it. So, if there is a collision, take the md5 hash of the
+ * the path, and use that as the filename instead.
+ */
- string dir;
- string suffix;
- string::size_type dash;
- string::size_type postfix;
+ SessionDirectory sdir (get_best_session_directory_for_new_audio());
+ string base = Glib::path_get_basename (path);
+ string newpath = Glib::build_filename (sdir.sound_path(), base);
+
+ if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
- dir = Glib::path_get_dirname (path);
- path = Glib::path_get_basename (path);
+ MD5 md5;
- /* '-' is not a legal character for the NAME part of the path */
+ md5.digestString (path.c_str());
+ md5.writeToString ();
+ base = md5.digestChars;
+
+ string ext = get_suffix (path);
- if ((dash = path.find_last_of ('-')) == string::npos) {
- return "";
+ if (!ext.empty()) {
+ base += '.';
+ base += ext;
}
+
+ newpath = Glib::build_filename (sdir.sound_path(), base);
- suffix = path.substr (dash+1);
-
- // Suffix is now everything after the dash. Now we need to eliminate
- // the nnnnn part, which is done by either finding a '%' or a '.'
+ /* if this collides, we're screwed */
- postfix = suffix.find_last_of ("%");
- if (postfix == string::npos) {
- postfix = suffix.find_last_of ('.');
+ if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
+ error << string_compose (_("Merging embedded file %1: name collision AND md5 hash collision!"), path) << endmsg;
+ return string();
}
- if (postfix != string::npos) {
- suffix = suffix.substr (postfix);
- } else {
- error << "Logic error in Session::change_source_path_by_name(), please report" << endl;
- return "";
- }
-
- const uint32_t limit = 10000;
- char buf[PATH_MAX+1];
+ }
- for (uint32_t cnt = 1; cnt <= limit; ++cnt) {
+ return newpath;
+}
- snprintf (buf, sizeof(buf), "%s-%u%s", newname.c_str(), cnt, suffix.c_str());
+bool
+Session::audio_source_name_is_unique (const string& name, uint32_t chan)
+{
+ std::vector<string> sdirs = source_search_path (DataType::AUDIO);
+ vector<space_and_path>::iterator i;
+ uint32_t existing = 0;
- if (!matching_unsuffixed_filename_exists_in (dir, buf)) {
- path = Glib::build_filename (dir, buf);
- break;
- }
+ for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
+
+ /* note that we search *without* the extension so that
+ we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf"
+ in the event that this new name is required for
+ a file format change.
+ */
- path = "";
+ const string spath = *i;
+
+ if (matching_unsuffixed_filename_exists_in (spath, name)) {
+ existing++;
+ break;
}
+
+ /* it is possible that we have the path already
+ * assigned to a source that has not yet been written
+ * (ie. the write source for a diskstream). we have to
+ * check this in order to make sure that our candidate
+ * path isn't used again, because that can lead to
+ * two Sources point to the same file with different
+ * notions of their removability.
+ */
+
+
+ string possible_path = Glib::build_filename (spath, name);
- if (path.empty()) {
- fatal << string_compose (_("FATAL ERROR! Could not find a suitable version of %1 for a rename"),
- newname) << endl;
- /*NOTREACHED*/
+ if (audio_source_by_path_and_channel (possible_path, chan)) {
+ existing++;
+ break;
}
}
- return path;
+ return (existing == 0);
}
-/** Return the full path (in some session directory) for a new within-session source.
- * \a name must be a session-unique name that does not contain slashes
- * (e.g. as returned by new_*_source_name)
- */
string
-Session::new_source_path_from_name (DataType type, const string& name)
+Session::format_audio_source_name (const string& legalized_base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required, uint32_t cnt, bool related_exists)
{
- assert(name.find("/") == string::npos);
-
- SessionDirectory sdir(get_best_session_directory_for_new_source());
-
- std::string p;
- if (type == DataType::AUDIO) {
- p = sdir.sound_path();
- } else if (type == DataType::MIDI) {
- p = sdir.midi_path();
+ ostringstream sstr;
+ const string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
+
+ if (destructive) {
+ sstr << 'T';
+ sstr << setfill ('0') << setw (4) << cnt;
+ sstr << legalized_base;
} else {
- error << "Unknown source type, unable to create file path" << endmsg;
- return "";
+ sstr << legalized_base;
+
+ if (take_required || related_exists) {
+ sstr << '-';
+ sstr << cnt;
+ }
}
+
+ if (nchan == 2) {
+ if (chan == 0) {
+ sstr << "%L";
+ } else {
+ sstr << "%R";
+ }
+ } else if (nchan > 2) {
+ if (nchan < 26) {
+ sstr << '%';
+ sstr << 'a' + chan;
+ } else {
+ /* XXX what? more than 26 channels! */
+ sstr << '%';
+ sstr << chan+1;
+ }
+ }
+
+ sstr << ext;
- return Glib::build_filename (p, name);
-}
-
-string
-Session::peak_path (string base) const
-{
- return Glib::build_filename (_session_dir->peak_path(), base + peakfile_suffix);
+ return sstr.str();
}
/** Return a unique name based on \a base for a new internal audio source */
string
-Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t chan, bool destructive)
+Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required)
{
uint32_t cnt;
- char buf[PATH_MAX+1];
- const uint32_t limit = 10000;
+ string possible_name;
+ const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name
string legalized;
- string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
+ bool some_related_source_name_exists = false;
- buf[0] = '\0';
legalized = legalize_for_path (base);
// Find a "version" of the base name that doesn't exist in any of the possible directories.
- for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) {
-
- vector<space_and_path>::iterator i;
- uint32_t existing = 0;
-
- for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
- if (destructive) {
-
- if (nchan < 2) {
- snprintf (buf, sizeof(buf), "T%04d-%s%s",
- cnt, legalized.c_str(), ext.c_str());
- } else if (nchan == 2) {
- if (chan == 0) {
- snprintf (buf, sizeof(buf), "T%04d-%s%%L%s",
- cnt, legalized.c_str(), ext.c_str());
- } else {
- snprintf (buf, sizeof(buf), "T%04d-%s%%R%s",
- cnt, legalized.c_str(), ext.c_str());
- }
- } else if (nchan < 26) {
- snprintf (buf, sizeof(buf), "T%04d-%s%%%c%s",
- cnt, legalized.c_str(), 'a' + chan, ext.c_str());
- } else {
- snprintf (buf, sizeof(buf), "T%04d-%s%s",
- cnt, legalized.c_str(), ext.c_str());
- }
-
- } else {
-
- if (nchan < 2) {
- snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str());
- } else if (nchan == 2) {
- if (chan == 0) {
- snprintf (buf, sizeof(buf), "%s-%u%%L%s", legalized.c_str(), cnt, ext.c_str());
- } else {
- snprintf (buf, sizeof(buf), "%s-%u%%R%s", legalized.c_str(), cnt, ext.c_str());
- }
- } else if (nchan < 26) {
- snprintf (buf, sizeof(buf), "%s-%u%%%c%s", legalized.c_str(), cnt, 'a' + chan, ext.c_str());
- } else {
- snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str());
- }
- }
-
- SessionDirectory sdir((*i).path);
-
- string spath = sdir.sound_path();
-
- /* note that we search *without* the extension so that
- we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf"
- in the event that this new name is required for
- a file format change.
- */
-
- if (matching_unsuffixed_filename_exists_in (spath, buf)) {
- existing++;
- break;
- }
- }
+ for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) {
- if (existing == 0) {
+ possible_name = format_audio_source_name (legalized, nchan, chan, destructive, take_required, cnt, some_related_source_name_exists);
+
+ if (audio_source_name_is_unique (possible_name, chan)) {
break;
}
+
+ some_related_source_name_exists = true;
if (cnt > limit) {
error << string_compose(
}
}
- return Glib::path_get_basename (buf);
-}
+ /* We've established that the new name does not exist in any session
+ * directory, so now find out which one we should use for this new
+ * audio source.
+ */
-/** Create a new within-session audio source */
-boost::shared_ptr<AudioFileSource>
-Session::create_audio_source_for_session (size_t n_chans, string const & n, uint32_t chan, bool destructive)
-{
- const string name = new_audio_source_name (n, n_chans, chan, destructive);
- const string path = new_source_path_from_name(DataType::AUDIO, name);
+ SessionDirectory sdir (get_best_session_directory_for_new_audio());
+
+ std::string s = Glib::build_filename (sdir.sound_path(), possible_name);
- return boost::dynamic_pointer_cast<AudioFileSource> (
- SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate()));
+ return s;
}
-/** Return a unique name based on \a base for a new internal MIDI source */
+/** Return a unique name based on \a owner_name for a new internal MIDI source */
string
-Session::new_midi_source_name (const string& base)
+Session::new_midi_source_path (const string& base)
{
uint32_t cnt;
char buf[PATH_MAX+1];
const uint32_t limit = 10000;
string legalized;
+ string possible_path;
+ string possible_name;
buf[0] = '\0';
legalized = legalize_for_path (base);
// Find a "version" of the file name that doesn't exist in any of the possible directories.
+ std::vector<string> sdirs = source_search_path(DataType::MIDI);
+
+ /* - the main session folder is the first in the vector.
+ * - after checking all locations for file-name uniqueness,
+ * we keep the one from the last iteration as new file name
+ * - midi files are small and should just be kept in the main session-folder
+ *
+ * -> reverse the array, check main session folder last and use that as location
+ * for MIDI files.
+ */
+ std::reverse(sdirs.begin(), sdirs.end());
+
for (cnt = 1; cnt <= limit; ++cnt) {
vector<space_and_path>::iterator i;
uint32_t existing = 0;
+
+ for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
- for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
-
- SessionDirectory sdir((*i).path);
-
- std::string p = Glib::build_filename (sdir.midi_path(), legalized);
+ snprintf (buf, sizeof(buf), "%s-%u.mid", legalized.c_str(), cnt);
+ possible_name = buf;
- snprintf (buf, sizeof(buf), "%s-%u.mid", p.c_str(), cnt);
+ possible_path = Glib::build_filename (*i, possible_name);
+
+ if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) {
+ existing++;
+ }
- if (Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) {
+ if (midi_source_by_path (possible_path)) {
existing++;
}
}
_("There are already %1 recordings for %2, which I consider too many."),
limit, base) << endmsg;
destroy ();
- throw failed_constructor();
+ return 0;
}
}
- return Glib::path_get_basename(buf);
+ /* No need to "find best location" for software/app-based RAID, because
+ MIDI is so small that we always put it in the same place.
+ */
+
+ return possible_path;
}
+/** Create a new within-session audio source */
+boost::shared_ptr<AudioFileSource>
+Session::create_audio_source_for_session (size_t n_chans, string const & base, uint32_t chan, bool destructive)
+{
+ const string path = new_audio_source_path (base, n_chans, chan, destructive, true);
+
+ if (!path.empty()) {
+ return boost::dynamic_pointer_cast<AudioFileSource> (
+ SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate()));
+ } else {
+ throw failed_constructor ();
+ }
+}
+
/** Create a new within-session MIDI source */
boost::shared_ptr<MidiSource>
-Session::create_midi_source_for_session (Track* track, string const & n)
+Session::create_midi_source_for_session (string const & basic_name)
{
- /* try to use the existing write source for the track, to keep numbering sane
- */
-
- if (track) {
- /*MidiTrack* mt = dynamic_cast<Track*> (track);
- assert (mt);
- */
+ const string path = new_midi_source_path (basic_name);
+
+ if (!path.empty()) {
+ return boost::dynamic_pointer_cast<SMFSource> (
+ SourceFactory::createWritable (
+ DataType::MIDI, *this, path, false, frame_rate()));
+ } else {
+ throw failed_constructor ();
+ }
+}
- list<boost::shared_ptr<Source> > l = track->steal_write_sources ();
+/** Create a new within-session MIDI source */
+boost::shared_ptr<MidiSource>
+Session::create_midi_source_by_stealing_name (boost::shared_ptr<Track> track)
+{
+ /* the caller passes in the track the source will be used in,
+ so that we can keep the numbering sane.
+
+ Rationale: a track with the name "Foo" that has had N
+ captures carried out so far will ALREADY have a write source
+ named "Foo-N+1.mid" waiting to be used for the next capture.
+
+ If we call new_midi_source_name() we will get "Foo-N+2". But
+ there is no region corresponding to "Foo-N+1", so when
+ "Foo-N+2" appears in the track, the gap presents the user
+ with odd behaviour - why did it skip past Foo-N+1?
+
+ We could explain this to the user in some odd way, but
+ instead we rename "Foo-N+1.mid" as "Foo-N+2.mid", and then
+ use "Foo-N+1" here.
+
+ If that attempted rename fails, we get "Foo-N+2.mid" anyway.
+ */
+
+ boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (track);
+ assert (mt);
+ std::string name = track->steal_write_source_name ();
- if (!l.empty()) {
- assert (boost::dynamic_pointer_cast<MidiSource> (l.front()));
- return boost::dynamic_pointer_cast<MidiSource> (l.front());
- }
+ if (name.empty()) {
+ return boost::shared_ptr<MidiSource>();
}
- const string name = new_midi_source_name (n);
- const string path = new_source_path_from_name (DataType::MIDI, name);
+ const string path = new_midi_source_path (name);
return boost::dynamic_pointer_cast<SMFSource> (
SourceFactory::createWritable (
void
Session::cancel_audition ()
{
+ if (!auditioner) {
+ return;
+ }
if (auditioner->auditioning()) {
auditioner->cancel_audition ();
AuditionActive (false); /* EMIT SIGNAL */
void
Session::ensure_buffers (ChanCount howmany)
{
- BufferManager::ensure_buffers (howmany);
+ BufferManager::ensure_buffers (howmany, bounce_processing() ? bounce_chunk_size : 0);
}
void
bool /*overwrite*/, vector<boost::shared_ptr<Source> >& srcs,
InterThreadInfo& itt,
boost::shared_ptr<Processor> endpoint, bool include_endpoint,
- bool for_export)
+ bool for_export, bool for_freeze)
{
boost::shared_ptr<Region> result;
boost::shared_ptr<Playlist> playlist;
boost::shared_ptr<AudioFileSource> fsource;
- uint32_t x;
- char buf[PATH_MAX+1];
ChanCount diskstream_channels (track.n_channels());
framepos_t position;
framecnt_t this_chunk;
framepos_t to_do;
+ framepos_t latency_skip;
BufferSet buffers;
- SessionDirectory sdir(get_best_session_directory_for_new_source ());
- const string sound_dir = sdir.sound_path();
framepos_t len = end - start;
bool need_block_size_reset = false;
- string ext;
ChanCount const max_proc = track.max_processor_streams ();
+ string legal_playlist_name;
+ string possible_path;
if (end <= start) {
error << string_compose (_("Cannot write a range where end <= start (e.g. %1 <= %2)"),
return result;
}
- const framecnt_t chunk_size = (256 * 1024)/4;
+ diskstream_channels = track.bounce_get_output_streams (diskstream_channels, endpoint,
+ include_endpoint, for_export, for_freeze);
+
+ if (diskstream_channels.n_audio() < 1) {
+ error << _("Cannot write a range with no audio.") << endmsg;
+ return result;
+ }
// block all process callback handling
block_processing ();
+ {
+ // synchronize with AudioEngine::process_callback()
+ // make sure processing is not currently running
+ // and processing_blocked() is honored before
+ // acquiring thread buffers
+ Glib::Threads::Mutex::Lock lm (_engine.process_lock());
+ }
+
+ _bounce_processing_active = true;
+
/* call tree *MUST* hold route_lock */
if ((playlist = track.playlist()) == 0) {
goto out;
}
- ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
+ legal_playlist_name = legalize_for_path (playlist->name());
for (uint32_t chan_n = 0; chan_n < diskstream_channels.n_audio(); ++chan_n) {
- for (x = 0; x < 99999; ++x) {
- snprintf (buf, sizeof(buf), "%s/%s-%d-bounce-%" PRIu32 "%s", sound_dir.c_str(), playlist->name().c_str(), chan_n, x+1, ext.c_str());
- if (!Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) {
- break;
- }
- }
-
- if (x == 99999) {
- error << string_compose (_("too many bounced versions of playlist \"%1\""), playlist->name()) << endmsg;
+ string base_name = string_compose ("%1-%2-bounce", playlist->name(), chan_n);
+ string path = new_audio_source_path (legal_playlist_name, diskstream_channels.n_audio(), chan_n, false, true);
+
+ if (path.empty()) {
goto out;
}
try {
fsource = boost::dynamic_pointer_cast<AudioFileSource> (
- SourceFactory::createWritable (DataType::AUDIO, *this, buf, false, frame_rate()));
+ SourceFactory::createWritable (DataType::AUDIO, *this, path, false, frame_rate()));
}
catch (failed_constructor& err) {
- error << string_compose (_("cannot create new audio file \"%1\" for %2"), buf, track.name()) << endmsg;
+ error << string_compose (_("cannot create new audio file \"%1\" for %2"), path, track.name()) << endmsg;
goto out;
}
*/
need_block_size_reset = true;
- track.set_block_size (chunk_size);
+ track.set_block_size (bounce_chunk_size);
+ _engine.main_thread()->get_buffers ();
position = start;
to_do = len;
+ latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export, for_freeze);
/* create a set of reasonably-sized buffers */
- buffers.ensure_buffers (DataType::AUDIO, max_proc.n_audio(), chunk_size);
+ for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) {
+ buffers.ensure_buffers(*t, max_proc.get(*t), bounce_chunk_size);
+ }
buffers.set_count (max_proc);
for (vector<boost::shared_ptr<Source> >::iterator src = srcs.begin(); src != srcs.end(); ++src) {
while (to_do && !itt.cancel) {
- this_chunk = min (to_do, chunk_size);
+ this_chunk = min (to_do, bounce_chunk_size);
- if (track.export_stuff (buffers, start, this_chunk, endpoint, include_endpoint, for_export)) {
+ if (track.export_stuff (buffers, start, this_chunk, endpoint, include_endpoint, for_export, for_freeze)) {
goto out;
}
+ start += this_chunk;
+ to_do -= this_chunk;
+ itt.progress = (float) (1.0 - ((double) to_do / len));
+
+ if (latency_skip >= bounce_chunk_size) {
+ latency_skip -= bounce_chunk_size;
+ continue;
+ }
+
+ const framecnt_t current_chunk = this_chunk - latency_skip;
+
uint32_t n = 0;
for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) {
boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
if (afs) {
- if (afs->write (buffers.get_audio(n).data(), this_chunk) != this_chunk) {
+ if (afs->write (buffers.get_audio(n).data(latency_skip), current_chunk) != current_chunk) {
goto out;
}
}
}
+ latency_skip = 0;
+ }
- start += this_chunk;
- to_do -= this_chunk;
+ /* post-roll, pick up delayed processor output */
+ latency_skip = track.bounce_get_latency (endpoint, include_endpoint, for_export, for_freeze);
- itt.progress = (float) (1.0 - ((double) to_do / len));
+ while (latency_skip && !itt.cancel) {
+ this_chunk = min (latency_skip, bounce_chunk_size);
+ latency_skip -= this_chunk;
+
+ buffers.silence (this_chunk, 0);
+ track.bounce_process (buffers, start, this_chunk, endpoint, include_endpoint, for_export, for_freeze);
+ uint32_t n = 0;
+ for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) {
+ boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
+
+ if (afs) {
+ if (afs->write (buffers.get_audio(n).data(), this_chunk) != this_chunk) {
+ goto out;
+ }
+ }
+ }
}
if (!itt.cancel) {
}
}
+ _bounce_processing_active = false;
if (need_block_size_reset) {
+ _engine.main_thread()->drop_buffers ();
track.set_block_size (get_block_size());
}
RouteRemovedFromRouteGroup (rg, r);
}
+boost::shared_ptr<RouteList>
+Session::get_tracks () const
+{
+ boost::shared_ptr<RouteList> rl = routes.reader ();
+ boost::shared_ptr<RouteList> tl (new RouteList);
+
+ for (RouteList::const_iterator r = rl->begin(); r != rl->end(); ++r) {
+ if (boost::dynamic_pointer_cast<Track> (*r)) {
+ if (!(*r)->is_auditioner()) {
+ tl->push_back (*r);
+ }
+ }
+ }
+ return tl;
+}
+
boost::shared_ptr<RouteList>
Session::get_routes_with_regions_at (framepos_t const p) const
{
}
}
-string
+std::vector<std::string>
Session::source_search_path (DataType type) const
{
- vector<string> s;
+ Searchpath sp;
if (session_dirs.size() == 1) {
switch (type) {
case DataType::AUDIO:
- s.push_back (_session_dir->sound_path());
+ sp.push_back (_session_dir->sound_path());
break;
case DataType::MIDI:
- s.push_back (_session_dir->midi_path());
+ sp.push_back (_session_dir->midi_path());
break;
}
} else {
SessionDirectory sdir (i->path);
switch (type) {
case DataType::AUDIO:
- s.push_back (sdir.sound_path());
+ sp.push_back (sdir.sound_path());
break;
case DataType::MIDI:
- s.push_back (sdir.midi_path());
+ sp.push_back (sdir.midi_path());
break;
}
}
if (type == DataType::AUDIO) {
const string sound_path_2X = _session_dir->sound_path_2X();
if (Glib::file_test (sound_path_2X, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_DIR)) {
- if (find (s.begin(), s.end(), sound_path_2X) == s.end()) {
- s.push_back (sound_path_2X);
+ if (find (sp.begin(), sp.end(), sound_path_2X) == sp.end()) {
+ sp.push_back (sound_path_2X);
}
}
}
- /* now check the explicit (possibly user-specified) search path
- */
-
- vector<string> dirs;
+ // now check the explicit (possibly user-specified) search path
switch (type) {
case DataType::AUDIO:
- split (config.get_audio_search_path (), dirs, ':');
+ sp += Searchpath(config.get_audio_search_path ());
break;
case DataType::MIDI:
- split (config.get_midi_search_path (), dirs, ':');
+ sp += Searchpath(config.get_midi_search_path ());
break;
}
- for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
- if (find (s.begin(), s.end(), *i) == s.end()) {
- s.push_back (*i);
- }
- }
-
- string search_path;
-
- for (vector<string>::iterator si = s.begin(); si != s.end(); ++si) {
- if (!search_path.empty()) {
- search_path += ':';
- }
- search_path += *si;
- }
-
- return search_path;
+ return sp;
}
void
Session::ensure_search_path_includes (const string& path, DataType type)
{
- string search_path;
- vector<string> dirs;
+ Searchpath sp;
if (path == ".") {
return;
switch (type) {
case DataType::AUDIO:
- search_path = config.get_audio_search_path ();
+ sp += Searchpath(config.get_audio_search_path ());
break;
case DataType::MIDI:
- search_path = config.get_midi_search_path ();
+ sp += Searchpath (config.get_midi_search_path ());
break;
}
- split (search_path, dirs, ':');
-
- for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
+ for (vector<std::string>::iterator i = sp.begin(); i != sp.end(); ++i) {
/* No need to add this new directory if it has the same inode as
an existing one; checking inode rather than name prevents duplicated
directories when we are using symlinks.
}
}
- if (!search_path.empty()) {
- search_path += ':';
+ sp += path;
+
+ switch (type) {
+ case DataType::AUDIO:
+ config.set_audio_search_path (sp.to_string());
+ break;
+ case DataType::MIDI:
+ config.set_midi_search_path (sp.to_string());
+ break;
}
+}
- search_path += path;
+void
+Session::remove_dir_from_search_path (const string& dir, DataType type)
+{
+ Searchpath sp;
switch (type) {
case DataType::AUDIO:
- config.set_audio_search_path (search_path);
+ sp = Searchpath(config.get_audio_search_path ());
break;
case DataType::MIDI:
- config.set_midi_search_path (search_path);
+ sp = Searchpath (config.get_midi_search_path ());
break;
}
+
+ sp -= dir;
+
+ switch (type) {
+ case DataType::AUDIO:
+ config.set_audio_search_path (sp.to_string());
+ break;
+ case DataType::MIDI:
+ config.set_midi_search_path (sp.to_string());
+ break;
+ }
+
}
boost::shared_ptr<Speakers>
DEBUG_TRACE (DEBUG::OrderKeys, "Sync Order Keys.\n");
+ reassign_track_numbers();
+
Route::SyncOrderKeys (); /* EMIT SIGNAL */
DEBUG_TRACE (DEBUG::OrderKeys, "\tsync done\n");