Merge branch 'waveview_hacks' of https://github.com/nmains/ardour into cairocanvas
[ardour.git] / libs / ardour / session.cc
index c9e0068431f80634da7db95374c3875f5524083e..b7b5667d0663bfd960f84329a12d3d49a7009262 100644 (file)
 
 #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/strsplit.h"
+#include "pbd/convert.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"
@@ -68,6 +69,7 @@
 #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"
@@ -81,6 +83,7 @@
 #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"
@@ -124,6 +127,7 @@ PBD::Signal0<void> Session::FeedbackDetected;
 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);
 
@@ -136,6 +140,7 @@ Session::Session (AudioEngine &eng,
        : 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)
@@ -191,6 +196,8 @@ Session::Session (AudioEngine &eng,
        , 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)
@@ -232,6 +239,7 @@ Session::Session (AudioEngine &eng,
        , 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)
@@ -260,6 +268,7 @@ Session::Session (AudioEngine &eng,
        ,  _speakers (new Speakers)
        , _order_hint (0)
        , ignore_route_processor_changes (false)
+       , _scene_changer (0)
        , _midi_ports (0)
        , _mmc (0)
 {
@@ -293,6 +302,9 @@ Session::Session (AudioEngine &eng,
                        throw failed_constructor ();
                }
 
+               /* load default session properties - if any */
+               config.load_state();
+
        } else {
 
                if (load_state (_current_snapshot_name)) {
@@ -490,6 +502,14 @@ Session::destroy ()
 
        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 ();
@@ -509,7 +529,6 @@ Session::destroy ()
 
        /* reset these three references to special routes before we do the usual route delete thing */
 
-       auditioner.reset ();
        _master_out.reset ();
        _monitor_out.reset ();
 
@@ -547,6 +566,8 @@ Session::destroy ()
        /* 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;
@@ -1873,6 +1894,7 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, boost:
 
   failed:
        if (!new_routes.empty()) {
+               StateProtector sp (this);
                add_routes (new_routes, true, true, true);
 
                if (instrument) {
@@ -2114,6 +2136,7 @@ Session::new_audio_track (int input_channels, int output_channels, TrackMode mod
 
   failed:
        if (!new_routes.empty()) {
+               StateProtector sp (this);
                add_routes (new_routes, true, true, true);
        }
 
@@ -2199,6 +2222,7 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r
 
   failure:
        if (!ret.empty()) {
+               StateProtector sp (this);
                add_routes (ret, false, true, true); // autoconnect outputs only
        }
 
@@ -2315,6 +2339,7 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template
 
   out:
        if (!ret.empty()) {
+               StateProtector sp (this);
                add_routes (ret, true, true, true);
                IO::enable_connecting ();
        }
@@ -2344,6 +2369,8 @@ Session::add_routes (RouteList& new_routes, bool input_auto_connect, bool output
                save_state (_current_snapshot_name);
        }
        
+       reassign_track_numbers();
+
        RouteAdded (new_routes); /* EMIT SIGNAL */
 }
 
@@ -2585,6 +2612,13 @@ Session::remove_route (boost::shared_ptr<Route> route)
                }
        }
 
+       /* 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) {
@@ -2621,6 +2655,7 @@ Session::remove_route (boost::shared_ptr<Route> route)
        if (save_state (_current_snapshot_name)) {
                save_history (_current_snapshot_name);
        }
+       reassign_track_numbers();
 }
 
 void
@@ -3037,6 +3072,42 @@ Session::route_by_remote_id (uint32_t id)
        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)
 {
@@ -3280,7 +3351,7 @@ Session::remove_source (boost::weak_ptr<Source> src)
                }
        }
 
-       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.
@@ -3305,7 +3376,7 @@ Session::source_by_id (const PBD::ID& id)
 }
 
 boost::shared_ptr<AudioFileSource>
-Session::source_by_path_and_channel (const string& path, uint16_t chn) const
+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.
@@ -3326,7 +3397,7 @@ Session::source_by_path_and_channel (const string& path, uint16_t chn) const
 }
 
 boost::shared_ptr<MidiSource>
-Session::source_by_path (const std::string& path) const
+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.
@@ -3366,121 +3437,179 @@ Session::count_sources_by_origin (const string& path)
        return cnt;
 }
 
-/** 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)
-{
-       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();
-       } else {
-               error << "Unknown source type, unable to create file path" << endmsg;
-               return "";
-       }
-
-       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 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_for_embedded (const std::string& path)
 {
-       uint32_t cnt;
-       char buf[PATH_MAX+1];
-       const uint32_t limit = 10000;
-       string legalized;
-       string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
+       /* 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.
+        */
 
-       buf[0] = '\0';
-       legalized = legalize_for_path (base);
+       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)) {
 
-       // 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) {
+               MD5 md5;
 
-               vector<space_and_path>::iterator i;
-               uint32_t existing = 0;
+               md5.digestString (path.c_str());
+               md5.writeToString ();
+               base = md5.digestChars;
+               
+               string ext = get_suffix (path);
 
-               for (i = session_dirs.begin(); i != session_dirs.end(); ++i) {
+               if (!ext.empty()) {
+                       base += '.';
+                       base += ext;
+               }
+               
+               newpath = Glib::build_filename (sdir.sound_path(), base);
 
-                       if (destructive) {
+               /* if this collides, we're screwed */
 
-                               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());
-                               }
+               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();
+               }
 
-                       } 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());
-                               }
-                       }
+       return newpath;
+}
 
-                       SessionDirectory sdir((*i).path);
+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;
 
-                       string spath = sdir.sound_path();
+       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.
+               */
 
-                       /* 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.
-                       */
+               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 (matching_unsuffixed_filename_exists_in (spath, buf)) {
-                               existing++;
-                               break;
-                       }
+               if (audio_source_by_path_and_channel (possible_path, chan)) {
+                       existing++;
+                       break;
+               }
+       }
 
-                       string possible_path = Glib::build_filename (spath, buf);
+       return (existing == 0);
+}
 
-                       if (source_by_path (possible_path)) {
-                               existing++;
-                               break;
-                       }
+string
+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)
+{
+       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 {
+               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;
 
-               if (existing == 0) {
+       return sstr.str();
+}
+
+/** Return a unique name based on \a base for a new internal audio source */
+string
+Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required)
+{
+       uint32_t cnt;
+       string possible_name;
+       const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name
+       string legalized;
+       bool some_related_source_name_exists = false;
+
+       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) {
+
+               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(
@@ -3491,54 +3620,62 @@ Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t cha
                }
        }
 
-       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 owner_name for a new internal MIDI source */
 string
-Session::new_midi_source_name (const string& owner_name)
+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 (owner_name);
+       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);
-                       
                        snprintf (buf, sizeof(buf), "%s-%u.mid", legalized.c_str(), cnt);
                        possible_name = buf;
 
-                       std::string possible_path = Glib::build_filename (sdir.midi_path(), possible_name);
+                       possible_path = Glib::build_filename (*i, possible_name);
                        
                        if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) {
                                existing++;
                        }
 
-                       if (source_by_path (possible_path)) {
+                       if (midi_source_by_path (possible_path)) {
                                existing++;
                        }
                }
@@ -3550,31 +3687,47 @@ Session::new_midi_source_name (const string& owner_name)
                if (cnt > limit) {
                        error << string_compose(
                                        _("There are already %1 recordings for %2, which I consider too many."),
-                                       limit, owner_name) << endmsg;
+                                       limit, base) << endmsg;
                        destroy ();
-                       throw failed_constructor();
+                       return 0;
                }
        }
 
-       return possible_name;
+       /* 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 (string const & basic_name)
 {
-       std::string name;
-
-       if (name.empty()) {
-               name = new_midi_source_name (basic_name);
+       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 ();
        }
-
-       const string path = new_source_path_from_name (DataType::MIDI, name);
-
-       return boost::dynamic_pointer_cast<SMFSource> (
-               SourceFactory::createWritable (
-                       DataType::MIDI, *this, path, false, frame_rate()));
 }
 
 /** Create a new within-session MIDI source */
@@ -3608,7 +3761,7 @@ Session::create_midi_source_by_stealing_name (boost::shared_ptr<Track> track)
                return boost::shared_ptr<MidiSource>();
        }
 
-       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 (
@@ -3686,6 +3839,9 @@ Session::audition_region (boost::shared_ptr<Region> r)
 void
 Session::cancel_audition ()
 {
+       if (!auditioner) {
+               return;
+       }
        if (auditioner->auditioning()) {
                auditioner->cancel_audition ();
                AuditionActive (false); /* EMIT SIGNAL */
@@ -3868,7 +4024,7 @@ Session::update_locations_after_tempo_map_change (Locations::LocationList& loc)
 void
 Session::ensure_buffers (ChanCount howmany)
 {
-       BufferManager::ensure_buffers (howmany);
+       BufferManager::ensure_buffers (howmany, bounce_processing() ? bounce_chunk_size : 0);
 }
 
 void
@@ -4109,24 +4265,22 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
                          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)"),
@@ -4134,41 +4288,52 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
                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;
                }
 
@@ -4181,13 +4346,17 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
         */
 
        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) {
@@ -4198,28 +4367,56 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
 
        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) {
@@ -4271,8 +4468,10 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
                }
        }
 
+       _bounce_processing_active = false;
 
        if (need_block_size_reset) {
+               _engine.main_thread()->drop_buffers ();
                track.set_block_size (get_block_size());
        }
 
@@ -4435,6 +4634,22 @@ Session::route_removed_from_route_group (RouteGroup* rg, boost::weak_ptr<Route>
        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
 {
@@ -4563,18 +4778,18 @@ Session::end_time_changed (framepos_t old)
        }
 }
 
-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 {
@@ -4582,10 +4797,10 @@ Session::source_search_path (DataType type) const
                        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;
                        }
                }
@@ -4594,49 +4809,30 @@ Session::source_search_path (DataType type) const
        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;
@@ -4644,16 +4840,14 @@ Session::ensure_search_path_includes (const string& path, DataType type)
 
        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.
@@ -4665,20 +4859,43 @@ Session::ensure_search_path_includes (const string& path, DataType type)
                }
        }
 
-       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>
@@ -4961,6 +5178,8 @@ Session::sync_order_keys ()
 
        DEBUG_TRACE (DEBUG::OrderKeys, "Sync Order Keys.\n");
 
+       reassign_track_numbers();
+
        Route::SyncOrderKeys (); /* EMIT SIGNAL */
 
        DEBUG_TRACE (DEBUG::OrderKeys, "\tsync done\n");