Fix some mangled whitespace (noop).
[ardour.git] / libs / ardour / session.cc
index 989b91606554adf7138538d71181733cbbc2f6fa..4b02d8ed7e6b127b319c0ae5e459369ff75f594e 100644 (file)
 
 #include <boost/algorithm/string/erase.hpp>
 
+#include "pbd/basename.h"
+#include "pbd/boost_debug.h"
+#include "pbd/convert.h"
 #include "pbd/convert.h"
 #include "pbd/error.h"
-#include "pbd/boost_debug.h"
-#include "pbd/stl_delete.h"
-#include "pbd/basename.h"
-#include "pbd/stacktrace.h"
 #include "pbd/file_utils.h"
-#include "pbd/convert.h"
-#include "pbd/unwind.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/analyser.h"
@@ -163,8 +164,6 @@ Session::Session (AudioEngine &eng,
        , _worst_input_latency (0)
        , _worst_track_latency (0)
        , _have_captured (false)
-       , _meter_hold (0)
-       , _meter_falloff (0)
        , _non_soloed_outs_muted (false)
        , _listen_cnt (0)
        , _solo_isolated_cnt (0)
@@ -479,6 +478,7 @@ Session::destroy ()
        /* clear state tree so that no references to objects are held any more */
 
        delete state_tree;
+       state_tree = 0;
 
        /* reset dynamic state version back to default */
 
@@ -488,9 +488,13 @@ Session::destroy ()
        delete _butler;
        _butler = 0;
        
-       delete midi_control_ui;
        delete _all_route_group;
 
+       DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n");
+       for (list<RouteGroup *>::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) {
+               delete *i;
+       }
+
        if (click_data != default_click) {
                delete [] click_data;
        }
@@ -556,16 +560,11 @@ Session::destroy ()
                sources.clear ();
        }
 
-       DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n");
-       for (list<RouteGroup *>::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) {
-
-               delete *i;
-       }
-
        /* not strictly necessary, but doing it here allows the shared_ptr debugging to work */
        playlists.reset ();
 
        delete _scene_changer; _scene_changer = 0;
+       delete midi_control_ui; midi_control_ui = 0;
 
        delete _mmc; _mmc = 0;
        delete _midi_ports; _midi_ports = 0;
@@ -880,7 +879,7 @@ Session::add_monitor_section ()
                return;
        }
 
-       boost::shared_ptr<Route> r (new Route (*this, _("monitor"), Route::MonitorOut, DataType::AUDIO));
+       boost::shared_ptr<Route> r (new Route (*this, _("Monitor"), Route::MonitorOut, DataType::AUDIO));
 
        if (r->init ()) {
                return;
@@ -1234,9 +1233,9 @@ Session::set_auto_punch_location (Location* location)
 
        punch_connections.drop_connections ();
 
-       location->start_changed.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_start_changed, this, _1));
-       location->end_changed.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_end_changed, this, _1));
-       location->changed.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_changed, this, _1));
+       location->StartChanged.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_start_changed, this, location));
+       location->EndChanged.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_end_changed, this, location));
+       location->Changed.connect_same_thread (punch_connections, boost::bind (&Session::auto_punch_changed, this, location));
 
        location->set_auto_punch (true, this);
 
@@ -1245,6 +1244,25 @@ Session::set_auto_punch_location (Location* location)
        auto_punch_location_changed (location);
 }
 
+void
+Session::set_session_extents (framepos_t start, framepos_t end)
+{
+       Location* existing;
+       if ((existing = _locations->session_range_location()) == 0) {
+               //if there is no existing session, we need to make a new session location  (should never happen)
+               existing = new Location (*this, 0, 0, _("session"), Location::IsSessionRange);
+       }
+       
+       if (end <= start) {
+               error << _("Session: you can't use that location for session start/end)") << endmsg;
+               return;
+       }
+
+       existing->set( start, end );
+       
+       set_dirty();
+}
+
 void
 Session::set_auto_loop_location (Location* location)
 {
@@ -1276,9 +1294,9 @@ Session::set_auto_loop_location (Location* location)
 
        loop_connections.drop_connections ();
 
-       location->start_changed.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, _1));
-       location->end_changed.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, _1));
-       location->changed.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, _1));
+       location->StartChanged.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
+       location->EndChanged.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
+       location->Changed.connect_same_thread (loop_connections, boost::bind (&Session::auto_loop_changed, this, location));
 
        location->set_auto_loop (true, this);
 
@@ -1292,51 +1310,165 @@ Session::set_auto_loop_location (Location* location)
 }
 
 void
-Session::locations_added (Location *)
+Session::update_skips (Location* loc, bool consolidate)
 {
-       set_dirty ();
+        Locations::LocationList skips;
+
+        if (consolidate) {
+
+                skips = consolidate_skips (loc);
+
+        } else {
+                Locations::LocationList all_locations = _locations->list ();
+                
+                for (Locations::LocationList::iterator l = all_locations.begin(); l != all_locations.end(); ++l) {
+                        if ((*l)->is_skip ()) {
+                                skips.push_back (*l);
+                        }
+                }
+        }
+
+        sync_locations_to_skips (skips);
+}
+
+Locations::LocationList
+Session::consolidate_skips (Location* loc)
+{
+        Locations::LocationList all_locations = _locations->list ();
+        Locations::LocationList skips;
+
+        for (Locations::LocationList::iterator l = all_locations.begin(); l != all_locations.end(); ) {
+
+                if (!(*l)->is_skip ()) {
+                        ++l;
+                        continue;
+                }
+
+                /* don't test against self */
+
+                if (*l == loc) {
+                        ++l;
+                        continue;
+                }
+                        
+                switch (Evoral::coverage ((*l)->start(), (*l)->end(), loc->start(), loc->end())) {
+                case Evoral::OverlapInternal:
+                case Evoral::OverlapExternal:
+                case Evoral::OverlapStart:
+                case Evoral::OverlapEnd:
+                        /* adjust new location to cover existing one */
+                        loc->set_start (min (loc->start(), (*l)->start()));
+                        loc->set_end (max (loc->end(), (*l)->end()));
+                        /* we don't need this one any more */
+                        _locations->remove (*l);
+                        /* the location has been deleted, so remove reference to it in our local list */
+                        l = all_locations.erase (l);
+                        break;
+
+                case Evoral::OverlapNone:
+                        skips.push_back (*l);
+                        ++l;
+                        break;
+                }
+        }
+
+        /* add the new one, which now covers the maximal appropriate range based on overlaps with existing skips */
+
+        skips.push_back (loc);
+
+        return skips;
 }
 
 void
-Session::locations_changed ()
+Session::sync_locations_to_skips (const Locations::LocationList& locations)
 {
-       _locations->apply (*this, &Session::handle_locations_changed);
+       clear_events (SessionEvent::Skip);
+
+       for (Locations::LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
+                
+               Location* location = *i;
+                
+               if (location->is_skipping()) {
+                       SessionEvent* ev = new SessionEvent (SessionEvent::Skip, SessionEvent::Add, location->start(), location->end(), 1.0);
+                       queue_event (ev);
+               }
+       }
 }
 
 void
-Session::handle_locations_changed (Locations::LocationList& locations)
+Session::location_added (Location *location)
 {
-       Locations::LocationList::iterator i;
-       Location* location;
-       bool set_loop = false;
-       bool set_punch = false;
+        if (location->is_auto_punch()) {
+                set_auto_punch_location (location);
+        }
 
-       for (i = locations.begin(); i != locations.end(); ++i) {
+        if (location->is_auto_loop()) {
+                set_auto_loop_location (location);
+        }
+        
+        if (location->is_session_range()) {
+                /* no need for any signal handling or event setting with the session range,
+                   because we keep a direct reference to it and use its start/end directly.
+                */
+                _session_range_location = location;
+        }
 
-               location =* i;
+        if (location->is_skip()) {
+                /* listen for per-location signals that require us to update skip-locate events */
 
-               if (location->is_auto_punch()) {
-                       set_auto_punch_location (location);
-                       set_punch = true;
-               }
-               if (location->is_auto_loop()) {
-                       set_auto_loop_location (location);
-                       set_loop = true;
-               }
+                location->StartChanged.connect_same_thread (skip_connections, boost::bind (&Session::update_skips, this, location, true));
+                location->EndChanged.connect_same_thread (skip_connections, boost::bind (&Session::update_skips, this, location, true));
+                location->Changed.connect_same_thread (skip_connections, boost::bind (&Session::update_skips, this, location, true));
+                location->FlagsChanged.connect_same_thread (skip_connections, boost::bind (&Session::update_skips, this, location, false));
 
-               if (location->is_session_range()) {
-                       _session_range_location = location;
-               }
-       }
+                update_skips (location, true);
+        }
 
-       if (!set_loop) {
-               set_auto_loop_location (0);
-       }
-       if (!set_punch) {
-               set_auto_punch_location (0);
-       }
+       set_dirty ();
+}
 
-       set_dirty();
+void
+Session::location_removed (Location *location)
+{
+        if (location->is_auto_loop()) {
+                set_auto_loop_location (0);
+        }
+        
+        if (location->is_auto_punch()) {
+                set_auto_punch_location (0);
+        }
+
+        if (location->is_session_range()) {
+                /* this is never supposed to happen */
+                error << _("programming error: session range removed!") << endl;
+        }
+
+        if (location->is_skip()) {
+                
+                update_skips (location, false);
+        }
+
+       set_dirty ();
+}
+
+void
+Session::locations_changed ()
+{
+        _locations->apply (*this, &Session::_locations_changed);
+}
+
+void
+Session::_locations_changed (const Locations::LocationList& locations)
+{
+        /* There was some mass-change in the Locations object. 
+
+           We might be re-adding a location here but it doesn't actually matter
+           for all the locations that the Session takes an interest in.
+        */
+
+       for (Locations::LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
+                location_added (*i);
+        }
 }
 
 void
@@ -1357,7 +1489,7 @@ Session::enable_record ()
                if (g_atomic_int_compare_and_exchange (&_record_status, rs, Recording)) {
 
                        _last_record_location = _transport_frame;
-                       _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe));
+                       send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe));
 
                        if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
                                set_track_monitor_input_status (true);
@@ -1378,7 +1510,7 @@ Session::disable_record (bool rt_context, bool force)
 
                if ((!Config->get_latched_record_enable () && !play_loop) || force) {
                        g_atomic_int_set (&_record_status, Disabled);
-                       _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit));
+                       send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit));
                } else {
                        if (rs == Recording) {
                                g_atomic_int_set (&_record_status, Enabled);
@@ -1432,7 +1564,7 @@ Session::maybe_enable_record ()
                        enable_record ();
                }
        } else {
-               _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause));
+               send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause));
                RecordStateChanged (); /* EMIT SIGNAL */
        }
 
@@ -1446,30 +1578,10 @@ Session::audible_frame () const
        framepos_t tf;
        framecnt_t offset;
 
-       /* the first of these two possible settings for "offset"
-          mean that the audible frame is stationary until
-          audio emerges from the latency compensation
-          "pseudo-pipeline".
-
-          the second means that the audible frame is stationary
-          until audio would emerge from a physical port
-          in the absence of any plugin latency compensation
-       */
-
        offset = worst_playback_latency ();
 
-       if (offset > current_block_size) {
-               offset -= current_block_size;
-       } else {
-               /* XXX is this correct? if we have no external
-                  physical connections and everything is internal
-                  then surely this is zero? still, how
-                  likely is that anyway?
-               */
-               offset = current_block_size;
-       }
-
        if (synced_to_engine()) {
+               /* Note: this is basically just sync-to-JACK */
                tf = _engine.transport_frame();
        } else {
                tf = _transport_frame;
@@ -2049,6 +2161,14 @@ Session::auto_connect_route (boost::shared_ptr<Route> route, ChanCount& existing
        }
 }
 
+void
+Session::reconnect_existing_routes (bool withLock, bool reconnect_master, bool reconnect_inputs, bool reconnect_outputs)
+{
+        /* TRX does stuff here, ardour does not (but probably should). This is called after an engine reset (in particular).
+         */
+}
+
+
 /** Caller must not hold process lock
  *  @param name_template string to use for the start of the name, or "" to use "Audio".
  */
@@ -2277,7 +2397,7 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template
                                /* generate a new name by adding a number to the end of the template name */
                                if (!find_route_name (route_name.c_str(), ++number, name, sizeof(name), true)) {
                                        fatal << _("Session: UINT_MAX routes? impossible!") << endmsg;
-                                       /*NOTREACHED*/
+                                       abort(); /*NOTREACHED*/
                                }
                        }
 
@@ -3350,7 +3470,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.
@@ -3442,94 +3562,172 @@ 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_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required)
+Session::new_audio_source_path_for_embedded (const std::string& path)
 {
-       uint32_t cnt;
-       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;
-
-       possible_name[0] = '\0';
-       legalized = legalize_for_path (base);
+       /* 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.
+        */
 
-       std::vector<string> sdirs = source_search_path(DataType::AUDIO);
+       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.
+               MD5 md5;
 
-       for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) {
+               md5.digestString (path.c_str());
+               md5.writeToString ();
+               base = md5.digestChars;
+               
+               string ext = get_suffix (path);
 
-               vector<space_and_path>::iterator i;
-               uint32_t existing = 0;
+               if (!ext.empty()) {
+                       base += '.';
+                       base += ext;
+               }
+               
+               newpath = Glib::build_filename (sdir.sound_path(), base);
 
-               for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
+               /* if this collides, we're screwed */
 
-                       ostringstream sstr;
+               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 (destructive) {
-                               sstr << 'T';
-                               sstr << setfill ('0') << setw (4) << cnt;
-                               sstr << legalized;
-                       } else {
-                               sstr << legalized;
-                               
-                               if (take_required || some_related_source_name_exists) {
-                                       sstr << '-';
-                                       sstr << cnt;
-                               }
-                       }
-                       
-                       if (nchan == 2) {
-                               if (chan == 0) {
-                                       sstr << "%L";
-                               } else {
-                                       sstr << "%R";
-                               }
-                       } else if (nchan > 2 && nchan < 26) {
-                               sstr << '%';
-                               sstr << 'a' + chan;
-                       } 
+       }
 
-                       sstr << ext;
+       return newpath;
+}
 
-                       possible_name = sstr.str();
-                       const string spath = (*i);
+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;
 
-                       /* 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.
-                       */
+       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.
+               */
 
-                       if (matching_unsuffixed_filename_exists_in (spath, possible_name)) {
-                               existing++;
-                               break;
-                       }
+               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);
 
-                       /* 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.
-                        */
+               if (audio_source_by_path_and_channel (possible_path, chan)) {
+                       existing++;
+                       break;
+               }
+       }
 
-                       string possible_path = Glib::build_filename (spath, possible_name);
+       return (existing == 0);
+}
 
-                       if (audio_source_by_path_and_channel (possible_path, chan)) {
-                               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) {
@@ -3553,7 +3751,7 @@ Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t cha
        return s;
 }
 
-/** Return a unique name based on \a owner_name for a new internal MIDI source */
+/** Return a unique name based on `base` for a new internal MIDI source */
 string
 Session::new_midi_source_path (const string& base)
 {
@@ -3682,7 +3880,11 @@ Session::create_midi_source_by_stealing_name (boost::shared_ptr<Track> track)
                return boost::shared_ptr<MidiSource>();
        }
 
-       const string path = new_midi_source_path (name);
+       /* MIDI files are small, just put them in the first location of the
+          session source search path.
+       */
+
+       const string path = Glib::build_filename (source_search_path (DataType::MIDI).front(), name);
 
        return boost::dynamic_pointer_cast<SMFSource> (
                SourceFactory::createWritable (
@@ -3856,7 +4058,7 @@ Session::available_capture_duration ()
                fatal << string_compose (_("programming error: %1"),
                                         X_("illegal native file data format"))
                      << endmsg;
-               /*NOTREACHED*/
+               abort(); /*NOTREACHED*/
        }
 
        double scale = 4096.0 / sample_bytes_on_disk;
@@ -3932,9 +4134,9 @@ Session::tempo_map_changed (const PropertyChange&)
 }
 
 void
-Session::update_locations_after_tempo_map_change (Locations::LocationList& loc)
+Session::update_locations_after_tempo_map_change (const Locations::LocationList& loc)
 {
-       for (Locations::LocationList::iterator i = loc.begin(); i != loc.end(); ++i) {
+       for (Locations::LocationList::const_iterator i = loc.begin(); i != loc.end(); ++i) {
                (*i)->recompute_frames_from_bbt ();
        }
 }
@@ -4182,7 +4384,7 @@ Session::freeze_all (InterThreadInfo& itt)
 }
 
 boost::shared_ptr<Region>
-Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
+Session::write_one_track (Track& 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,
@@ -4190,7 +4392,7 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
 {
        boost::shared_ptr<Region> result;
        boost::shared_ptr<Playlist> playlist;
-       boost::shared_ptr<AudioFileSource> fsource;
+       boost::shared_ptr<Source> source;
        ChanCount diskstream_channels (track.n_channels());
        framepos_t position;
        framecnt_t this_chunk;
@@ -4212,8 +4414,8 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
        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;
+       if (diskstream_channels.n(track.data_type()) < 1) {
+               error << _("Cannot write a range with no data.") << endmsg;
                return result;
        }
 
@@ -4239,26 +4441,27 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
 
        legal_playlist_name = legalize_for_path (playlist->name());
 
-       for (uint32_t chan_n = 0; chan_n < diskstream_channels.n_audio(); ++chan_n) {
+       for (uint32_t chan_n = 0; chan_n < diskstream_channels.n(track.data_type()); ++chan_n) {
 
                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);
+               string path = ((track.data_type() == DataType::AUDIO)
+                              ? new_audio_source_path (legal_playlist_name, diskstream_channels.n_audio(), chan_n, false, true)
+                              : new_midi_source_path (legal_playlist_name));
                
                if (path.empty()) {
                        goto out;
                }
 
                try {
-                       fsource = boost::dynamic_pointer_cast<AudioFileSource> (
-                               SourceFactory::createWritable (DataType::AUDIO, *this, path, false, frame_rate()));
+                       source = SourceFactory::createWritable (track.data_type(), *this, path, false, frame_rate());
                }
 
                catch (failed_constructor& err) {
-                       error << string_compose (_("cannot create new audio file \"%1\" for %2"), path, track.name()) << endmsg;
+                       error << string_compose (_("cannot create new file \"%1\" for %2"), path, track.name()) << endmsg;
                        goto out;
                }
 
-               srcs.push_back (fsource);
+               srcs.push_back (source);
        }
 
        /* tell redirects that care that we are about to use a much larger
@@ -4308,11 +4511,20 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
                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);
+                       boost::shared_ptr<MidiSource> ms;
 
                        if (afs) {
                                if (afs->write (buffers.get_audio(n).data(latency_skip), current_chunk) != current_chunk) {
                                        goto out;
                                }
+                       } else if ((ms = boost::dynamic_pointer_cast<MidiSource>(*src))) {
+                               Source::Lock lock(ms->mutex());
+                               ms->mark_streaming_write_started(lock);
+
+                               const MidiBuffer& buf = buffers.get_midi(0);
+                               for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
+                                       ms->append_event_frames(lock, *i, ms->timeline_position());
+                               }
                        }
                }
                latency_skip = 0;
@@ -4349,10 +4561,14 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
 
                for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src) {
                        boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
+                       boost::shared_ptr<MidiSource> ms;
 
                        if (afs) {
                                afs->update_header (position, *xnow, now);
                                afs->flush_header ();
+                       } else if ((ms = boost::dynamic_pointer_cast<MidiSource>(*src))) {
+                               Source::Lock lock(ms->mutex());
+                               ms->mark_streaming_write_completed(lock);
                        }
                }
 
@@ -4371,12 +4587,7 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end,
   out:
        if (!result) {
                for (vector<boost::shared_ptr<Source> >::iterator src = srcs.begin(); src != srcs.end(); ++src) {
-                       boost::shared_ptr<AudioFileSource> afs = boost::dynamic_pointer_cast<AudioFileSource>(*src);
-
-                       if (afs) {
-                               afs->mark_for_remove ();
-                       }
-
+                       (*src)->mark_for_remove ();
                        (*src)->drop_references ();
                }
 
@@ -4555,6 +4766,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
 {
@@ -4776,6 +5003,33 @@ Session::ensure_search_path_includes (const string& path, DataType type)
        }
 }
 
+void
+Session::remove_dir_from_search_path (const string& dir, DataType type)
+{
+       Searchpath sp;
+
+       switch (type) {
+       case DataType::AUDIO:
+               sp = Searchpath(config.get_audio_search_path ());
+               break;
+       case DataType::MIDI:
+               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>
 Session::get_speakers()
 {