#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"
, _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)
/* 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 */
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;
}
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;
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;
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);
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)
{
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);
}
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
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);
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);
enable_record ();
}
} else {
- _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause));
+ send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause));
RecordStateChanged (); /* EMIT SIGNAL */
}
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;
}
}
+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".
*/
/* 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*/
}
}
}
}
- 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 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) {
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)
{
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 (
fatal << string_compose (_("programming error: %1"),
X_("illegal native file data format"))
<< endmsg;
- /*NOTREACHED*/
+ abort(); /*NOTREACHED*/
}
double scale = 4096.0 / sample_bytes_on_disk;
}
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 ();
}
}
}
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,
{
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;
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;
}
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
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;
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);
}
}
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 ();
}
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
{
}
}
+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()
{