X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession.cc;h=3bdee5d6a115a22393a55620e95caaab5b27167f;hb=b68a4e5cdc59ffb81c1952ac9cad63ddf0c76d06;hp=53d670a1f41bfa881abe8d1c26f3e00893de689c;hpb=10933e200369ecceb2c8b3a52be41b930955d269;p=ardour.git diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 53d670a1f4..3bdee5d6a1 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -36,15 +36,16 @@ #include -#include "pbd/error.h" -#include "pbd/boost_debug.h" -#include "pbd/pathscanner.h" -#include "pbd/stl_delete.h" #include "pbd/basename.h" -#include "pbd/stacktrace.h" -#include "pbd/file_utils.h" +#include "pbd/boost_debug.h" +#include "pbd/convert.h" #include "pbd/convert.h" -#include "pbd/strsplit.h" +#include "pbd/error.h" +#include "pbd/file_utils.h" +#include "pbd/md5.h" +#include "pbd/search_path.h" +#include "pbd/stacktrace.h" +#include "pbd/stl_delete.h" #include "pbd/unwind.h" #include "ardour/amp.h" @@ -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" @@ -88,6 +91,7 @@ #include "ardour/smf_source.h" #include "ardour/source_factory.h" #include "ardour/speakers.h" +#include "ardour/track.h" #include "ardour/utils.h" #include "midi++/port.h" @@ -123,6 +127,7 @@ PBD::Signal0 Session::FeedbackDetected; PBD::Signal0 Session::SuccessfulGraphSort; PBD::Signal2 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); @@ -135,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) @@ -158,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) @@ -190,6 +194,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) @@ -203,6 +209,7 @@ Session::Session (AudioEngine &eng, , cumulative_rf_motion (0) , rf_scale (1.0) , _locations (new Locations (*this)) + , _ignore_skips_updates (false) , step_speed (0) , outbound_mtc_timecode_frame (0) , next_quarter_frame_to_send (-1) @@ -231,6 +238,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) @@ -259,6 +267,7 @@ Session::Session (AudioEngine &eng, , _speakers (new Speakers) , _order_hint (0) , ignore_route_processor_changes (false) + , _scene_changer (0) , _midi_ports (0) , _mmc (0) { @@ -292,6 +301,9 @@ Session::Session (AudioEngine &eng, throw failed_constructor (); } + /* load default session properties - if any */ + config.load_state(); + } else { if (load_state (_current_snapshot_name)) { @@ -467,6 +479,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 */ @@ -476,9 +489,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::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) { + delete *i; + } + if (click_data != default_click) { delete [] click_data; } @@ -489,6 +506,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 (); @@ -508,7 +533,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 (); @@ -526,23 +550,23 @@ Session::destroy () } routes.flush (); - DEBUG_TRACE (DEBUG::Destruction, "delete sources\n"); - for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) { - DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for source %1 ; pre-ref = %2\n", i->second->name(), i->second.use_count())); - i->second->drop_references (); - } - - sources.clear (); - - DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n"); - for (list::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) { + { + DEBUG_TRACE (DEBUG::Destruction, "delete sources\n"); + Glib::Threads::Mutex::Lock lm (source_lock); + for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) { + DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for source %1 ; pre-ref = %2\n", i->second->name(), i->second.use_count())); + i->second->drop_references (); + } - delete *i; + sources.clear (); } /* 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; delete _locations; _locations = 0; @@ -856,7 +880,7 @@ Session::add_monitor_section () return; } - boost::shared_ptr r (new Route (*this, _("monitor"), Route::MonitorOut, DataType::AUDIO)); + boost::shared_ptr r (new Route (*this, _("Monitor"), Route::MonitorOut, DataType::AUDIO)); if (r->init ()) { return; @@ -1182,6 +1206,7 @@ Session::auto_loop_changed (Location* location) } last_loopend = location->end(); + set_dirty (); } void @@ -1210,9 +1235,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); @@ -1221,6 +1246,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) { @@ -1252,9 +1296,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); @@ -1268,51 +1312,179 @@ Session::set_auto_loop_location (Location* location) } void -Session::locations_added (Location *) +Session::update_loop (Location*) { set_dirty (); } void -Session::locations_changed () +Session::update_marks (Location*) { - _locations->apply (*this, &Session::handle_locations_changed); + set_dirty (); } void -Session::handle_locations_changed (Locations::LocationList& locations) +Session::update_skips (Location* loc, bool consolidate) { - Locations::LocationList::iterator i; - Location* location; - bool set_loop = false; - bool set_punch = false; + if (_ignore_skips_updates) { + return; + } + + Locations::LocationList skips; - for (i = locations.begin(); i != locations.end(); ++i) { + if (consolidate) { + PBD::Unwinder uw (_ignore_skips_updates, true); + consolidate_skips (loc); + } - location =* i; + sync_locations_to_skips (); + + set_dirty (); +} - 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; - } +void +Session::consolidate_skips (Location* loc) +{ + Locations::LocationList all_locations = _locations->list (); + + 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: + ++l; + break; + } + } +} + +void +Session::sync_locations_to_skips () +{ + /* This happens asynchronously (in the audioengine thread). After the clear is done, we will call + * Session::_sync_locations_to_skips() from the audioengine thread. + */ + clear_events (SessionEvent::Skip, boost::bind (&Session::_sync_locations_to_skips, this)); +} + +void +Session::_sync_locations_to_skips () +{ + /* called as a callback after existing Skip events have been cleared from a realtime audioengine thread */ - if (location->is_session_range()) { - _session_range_location = location; + Locations::LocationList const & locs (_locations->list()); + + for (Locations::LocationList::const_iterator i = locs.begin(); i != locs.end(); ++i) { + + Location* location = *i; + + if (location->is_skip() && location->is_skipping()) { + SessionEvent* ev = new SessionEvent (SessionEvent::Skip, SessionEvent::Add, location->start(), location->end(), 1.0); + queue_event (ev); } } +} - if (!set_loop) { - set_auto_loop_location (0); - } - if (!set_punch) { - set_auto_punch_location (0); - } - set_dirty(); +void +Session::location_added (Location *location) +{ + if (location->is_auto_punch()) { + set_auto_punch_location (location); + } + + 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; + } + + if (location->is_skip()) { + /* listen for per-location signals that require us to update skip-locate events */ + + location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true)); + location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true)); + location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true)); + location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, false)); + + update_skips (location, true); + } + + set_dirty (); +} + +void +Session::location_removed (Location *location) +{ + if (location->is_auto_loop()) { + set_auto_loop_location (0); + set_track_loop (false); + } + + 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 @@ -1333,7 +1505,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); @@ -1354,7 +1526,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); @@ -1408,7 +1580,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 */ } @@ -1422,30 +1594,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; @@ -1869,6 +2021,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) { @@ -2024,6 +2177,14 @@ Session::auto_connect_route (boost::shared_ptr 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". */ @@ -2110,6 +2271,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); } @@ -2195,6 +2357,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 } @@ -2250,7 +2413,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*/ } } @@ -2311,6 +2474,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 (); } @@ -2340,6 +2504,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 */ } @@ -2581,6 +2747,13 @@ Session::remove_route (boost::shared_ptr 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 uw (ignore_route_processor_changes, true); + route->remove_aux_or_listen (_monitor_out); + } + boost::shared_ptr mt = boost::dynamic_pointer_cast (route); if (mt && mt->step_editing()) { if (_step_editors > 0) { @@ -2617,6 +2790,7 @@ Session::remove_route (boost::shared_ptr route) if (save_state (_current_snapshot_name)) { save_history (_current_snapshot_name); } + reassign_track_numbers(); } void @@ -3033,6 +3207,42 @@ Session::route_by_remote_id (uint32_t id) return boost::shared_ptr ((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 (*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 t = boost::dynamic_pointer_cast (*i); + if (t) { + t->resync_track_name(); + } + } + // trigger GUI re-layout + config.ParameterChanged("track-name-number"); + } +} + void Session::playlist_region_added (boost::weak_ptr w) { @@ -3276,7 +3486,7 @@ Session::remove_source (boost::weak_ptr 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. @@ -3300,12 +3510,16 @@ Session::source_by_id (const PBD::ID& id) return source; } -boost::shared_ptr -Session::source_by_path_and_channel (const string& path, uint16_t chn) +boost::shared_ptr +Session::audio_source_by_path_and_channel (const string& path, uint16_t chn) const { + /* Restricted to audio files because only audio sources have channel + as a property. + */ + Glib::Threads::Mutex::Lock lm (source_lock); - for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) { + for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) { boost::shared_ptr afs = boost::dynamic_pointer_cast(i->second); @@ -3313,7 +3527,31 @@ Session::source_by_path_and_channel (const string& path, uint16_t chn) return afs; } } - return boost::shared_ptr(); + + return boost::shared_ptr(); +} + +boost::shared_ptr +Session::midi_source_by_path (const std::string& path) const +{ + /* Restricted to MIDI files because audio sources require a channel + for unique identification, in addition to a path. + */ + + Glib::Threads::Mutex::Lock lm (source_lock); + + for (SourceMap::const_iterator s = sources.begin(); s != sources.end(); ++s) { + boost::shared_ptr ms + = boost::dynamic_pointer_cast(s->second); + boost::shared_ptr fs + = boost::dynamic_pointer_cast(s->second); + + if (ms && fs && fs->path() == path) { + return ms; + } + } + + return boost::shared_ptr(); } uint32_t @@ -3334,219 +3572,179 @@ Session::count_sources_by_origin (const string& path) return cnt; } - string -Session::change_source_path_by_name (string path, string oldname, string newname, bool destructive) +Session::peak_path (string base) const { - string look_for; - string old_basename = PBD::basename_nosuffix (oldname); - string new_legalized = legalize_for_path (newname); - - /* note: we know (or assume) the old path is already valid */ - - if (destructive) { - - /* destructive file sources have a name of the form: - - /path/to/Tnnnn-NAME(%[LR])?.wav - - the task here is to replace NAME with the new name. - */ - - string dir; - string prefix; - string::size_type dash; - - dir = Glib::path_get_dirname (path); - path = Glib::path_get_basename (path); - - /* '-' is not a legal character for the NAME part of the path */ - - if ((dash = path.find_last_of ('-')) == string::npos) { - return ""; - } - - prefix = path.substr (0, dash); - - path += prefix; - path += '-'; - path += new_legalized; - path += native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO); - path = Glib::build_filename (dir, path); - - } else { - - /* non-destructive file sources have a name of the form: - - /path/to/NAME-nnnnn(%[LR])?.ext + return Glib::build_filename (_session_dir->peak_path(), base + peakfile_suffix); +} - the task here is to replace NAME with the new name. - */ +string +Session::new_audio_source_path_for_embedded (const std::string& path) +{ + /* embedded source: + * + * we know that the filename is already unique because it exists + * out in the filesystem. + * + * However, when we bring it into the session, we could get a + * collision. + * + * Eg. two embedded files: + * + * /foo/bar/baz.wav + * /frob/nic/baz.wav + * + * When merged into session, these collide. + * + * There will not be a conflict with in-memory sources + * because when the source was created we already picked + * a unique name for it. + * + * This collision is not likely to be common, but we have to guard + * against it. So, if there is a collision, take the md5 hash of the + * the path, and use that as the filename instead. + */ - string dir; - string suffix; - string::size_type dash; - string::size_type postfix; + SessionDirectory sdir (get_best_session_directory_for_new_audio()); + string base = Glib::path_get_basename (path); + string newpath = Glib::build_filename (sdir.sound_path(), base); + + if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) { - dir = Glib::path_get_dirname (path); - path = Glib::path_get_basename (path); + MD5 md5; - /* '-' is not a legal character for the NAME part of the path */ + md5.digestString (path.c_str()); + md5.writeToString (); + base = md5.digestChars; + + string ext = get_suffix (path); - if ((dash = path.find_last_of ('-')) == string::npos) { - return ""; + if (!ext.empty()) { + base += '.'; + base += ext; } + + newpath = Glib::build_filename (sdir.sound_path(), base); - suffix = path.substr (dash+1); - - // Suffix is now everything after the dash. Now we need to eliminate - // the nnnnn part, which is done by either finding a '%' or a '.' - - postfix = suffix.find_last_of ("%"); - if (postfix == string::npos) { - postfix = suffix.find_last_of ('.'); - } + /* if this collides, we're screwed */ - if (postfix != string::npos) { - suffix = suffix.substr (postfix); - } else { - error << "Logic error in Session::change_source_path_by_name(), please report" << endl; - return ""; + 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(); } - const uint32_t limit = 10000; - char buf[PATH_MAX+1]; + } - for (uint32_t cnt = 1; cnt <= limit; ++cnt) { + return newpath; +} - snprintf (buf, sizeof(buf), "%s-%u%s", newname.c_str(), cnt, suffix.c_str()); +bool +Session::audio_source_name_is_unique (const string& name, uint32_t chan) +{ + std::vector sdirs = source_search_path (DataType::AUDIO); + vector::iterator i; + uint32_t existing = 0; - if (!matching_unsuffixed_filename_exists_in (dir, buf)) { - path = Glib::build_filename (dir, buf); - break; - } + for (vector::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) { + + /* note that we search *without* the extension so that + we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf" + in the event that this new name is required for + a file format change. + */ - path = ""; + const string spath = *i; + + if (matching_unsuffixed_filename_exists_in (spath, name)) { + existing++; + break; } + + /* it is possible that we have the path already + * assigned to a source that has not yet been written + * (ie. the write source for a diskstream). we have to + * check this in order to make sure that our candidate + * path isn't used again, because that can lead to + * two Sources point to the same file with different + * notions of their removability. + */ + + + string possible_path = Glib::build_filename (spath, name); - if (path.empty()) { - fatal << string_compose (_("FATAL ERROR! Could not find a suitable version of %1 for a rename"), - newname) << endl; - /*NOTREACHED*/ + if (audio_source_by_path_and_channel (possible_path, chan)) { + existing++; + break; } } - return path; + return (existing == 0); } -/** Return the full path (in some session directory) for a new within-session source. - * \a name must be a session-unique name that does not contain slashes - * (e.g. as returned by new_*_source_name) - */ string -Session::new_source_path_from_name (DataType type, const string& name) +Session::format_audio_source_name (const string& legalized_base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required, uint32_t cnt, bool related_exists) { - assert(name.find("/") == string::npos); - - SessionDirectory sdir(get_best_session_directory_for_new_source()); - - std::string p; - if (type == DataType::AUDIO) { - p = sdir.sound_path(); - } else if (type == DataType::MIDI) { - p = sdir.midi_path(); + ostringstream sstr; + const string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO); + + if (destructive) { + sstr << 'T'; + sstr << setfill ('0') << setw (4) << cnt; + sstr << legalized_base; } else { - error << "Unknown source type, unable to create file path" << endmsg; - return ""; + sstr << legalized_base; + + if (take_required || related_exists) { + sstr << '-'; + sstr << cnt; + } } + + if (nchan == 2) { + if (chan == 0) { + sstr << "%L"; + } else { + sstr << "%R"; + } + } else if (nchan > 2) { + if (nchan < 26) { + sstr << '%'; + sstr << 'a' + chan; + } else { + /* XXX what? more than 26 channels! */ + sstr << '%'; + sstr << chan+1; + } + } + + sstr << ext; - return Glib::build_filename (p, name); -} - -string -Session::peak_path (string base) const -{ - return Glib::build_filename (_session_dir->peak_path(), base + peakfile_suffix); + return sstr.str(); } /** Return a unique name based on \a base for a new internal audio source */ string -Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t chan, bool destructive) +Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required) { uint32_t cnt; - char buf[PATH_MAX+1]; - const uint32_t limit = 10000; + string possible_name; + const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name string legalized; - string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO); + bool some_related_source_name_exists = false; - buf[0] = '\0'; legalized = legalize_for_path (base); // Find a "version" of the base name that doesn't exist in any of the possible directories. - for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) { - - vector::iterator i; - uint32_t existing = 0; - - for (i = session_dirs.begin(); i != session_dirs.end(); ++i) { - - if (destructive) { - - if (nchan < 2) { - snprintf (buf, sizeof(buf), "T%04d-%s%s", - cnt, legalized.c_str(), ext.c_str()); - } else if (nchan == 2) { - if (chan == 0) { - snprintf (buf, sizeof(buf), "T%04d-%s%%L%s", - cnt, legalized.c_str(), ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "T%04d-%s%%R%s", - cnt, legalized.c_str(), ext.c_str()); - } - } else if (nchan < 26) { - snprintf (buf, sizeof(buf), "T%04d-%s%%%c%s", - cnt, legalized.c_str(), 'a' + chan, ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "T%04d-%s%s", - cnt, legalized.c_str(), ext.c_str()); - } - - } else { - - if (nchan < 2) { - snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str()); - } else if (nchan == 2) { - if (chan == 0) { - snprintf (buf, sizeof(buf), "%s-%u%%L%s", legalized.c_str(), cnt, ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "%s-%u%%R%s", legalized.c_str(), cnt, ext.c_str()); - } - } else if (nchan < 26) { - snprintf (buf, sizeof(buf), "%s-%u%%%c%s", legalized.c_str(), cnt, 'a' + chan, ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str()); - } - } - SessionDirectory sdir((*i).path); - - string spath = sdir.sound_path(); - - /* note that we search *without* the extension so that - we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf" - in the event that this new name is required for - a file format change. - */ - - if (matching_unsuffixed_filename_exists_in (spath, buf)) { - existing++; - break; - } - } + for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) { - if (existing == 0) { + possible_name = format_audio_source_name (legalized, nchan, chan, destructive, take_required, cnt, some_related_source_name_exists); + + if (audio_source_name_is_unique (possible_name, chan)) { break; } + + some_related_source_name_exists = true; if (cnt > limit) { error << string_compose( @@ -3557,47 +3755,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 -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 ( - SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate())); + return s; } -/** Return a unique name based on \a base for a new internal MIDI source */ +/** Return a unique name based on `base` for a new internal MIDI source */ string -Session::new_midi_source_name (const string& base) +Session::new_midi_source_path (const string& base) { uint32_t cnt; char buf[PATH_MAX+1]; const uint32_t limit = 10000; string legalized; + string possible_path; + string possible_name; buf[0] = '\0'; legalized = legalize_for_path (base); // Find a "version" of the file name that doesn't exist in any of the possible directories. + std::vector 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::iterator i; uint32_t existing = 0; + + for (vector::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) { - for (i = session_dirs.begin(); i != session_dirs.end(); ++i) { - - SessionDirectory sdir((*i).path); - - std::string p = Glib::build_filename (sdir.midi_path(), legalized); + snprintf (buf, sizeof(buf), "%s-%u.mid", legalized.c_str(), cnt); + possible_name = buf; - snprintf (buf, sizeof(buf), "%s-%u.mid", p.c_str(), cnt); + possible_path = Glib::build_filename (*i, possible_name); + + if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) { + existing++; + } - if (Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) { + if (midi_source_by_path (possible_path)) { existing++; } } @@ -3611,36 +3824,83 @@ Session::new_midi_source_name (const string& base) _("There are already %1 recordings for %2, which I consider too many."), limit, base) << endmsg; destroy (); - throw failed_constructor(); + return 0; } } - return Glib::path_get_basename(buf); + /* No need to "find best location" for software/app-based RAID, because + MIDI is so small that we always put it in the same place. + */ + + return possible_path; } +/** Create a new within-session audio source */ +boost::shared_ptr +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 ( + SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate())); + } else { + throw failed_constructor (); + } +} + /** Create a new within-session MIDI source */ boost::shared_ptr -Session::create_midi_source_for_session (Track* track, string const & n) +Session::create_midi_source_for_session (string const & basic_name) { - /* try to use the existing write source for the track, to keep numbering sane - */ - - if (track) { - /*MidiTrack* mt = dynamic_cast (track); - assert (mt); - */ + const string path = new_midi_source_path (basic_name); + + if (!path.empty()) { + return boost::dynamic_pointer_cast ( + SourceFactory::createWritable ( + DataType::MIDI, *this, path, false, frame_rate())); + } else { + throw failed_constructor (); + } +} - list > l = track->steal_write_sources (); +/** Create a new within-session MIDI source */ +boost::shared_ptr +Session::create_midi_source_by_stealing_name (boost::shared_ptr track) +{ + /* the caller passes in the track the source will be used in, + so that we can keep the numbering sane. + + Rationale: a track with the name "Foo" that has had N + captures carried out so far will ALREADY have a write source + named "Foo-N+1.mid" waiting to be used for the next capture. + + If we call new_midi_source_name() we will get "Foo-N+2". But + there is no region corresponding to "Foo-N+1", so when + "Foo-N+2" appears in the track, the gap presents the user + with odd behaviour - why did it skip past Foo-N+1? + + We could explain this to the user in some odd way, but + instead we rename "Foo-N+1.mid" as "Foo-N+2.mid", and then + use "Foo-N+1" here. + + If that attempted rename fails, we get "Foo-N+2.mid" anyway. + */ + + boost::shared_ptr mt = boost::dynamic_pointer_cast (track); + assert (mt); + std::string name = track->steal_write_source_name (); - if (!l.empty()) { - assert (boost::dynamic_pointer_cast (l.front())); - return boost::dynamic_pointer_cast (l.front()); - } + if (name.empty()) { + return boost::shared_ptr(); } - const string name = new_midi_source_name (n); - const string path = new_source_path_from_name (DataType::MIDI, 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 ( SourceFactory::createWritable ( @@ -3718,6 +3978,9 @@ Session::audition_region (boost::shared_ptr r) void Session::cancel_audition () { + if (!auditioner) { + return; + } if (auditioner->auditioning()) { auditioner->cancel_audition (); AuditionActive (false); /* EMIT SIGNAL */ @@ -3811,7 +4074,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; @@ -3887,9 +4150,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 (); } } @@ -3900,7 +4163,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 @@ -4137,28 +4400,26 @@ Session::freeze_all (InterThreadInfo& itt) } boost::shared_ptr -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 >& srcs, InterThreadInfo& itt, boost::shared_ptr endpoint, bool include_endpoint, - bool for_export) + bool for_export, bool for_freeze) { boost::shared_ptr result; boost::shared_ptr playlist; - boost::shared_ptr fsource; - uint32_t x; - char buf[PATH_MAX+1]; + boost::shared_ptr source; 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)"), @@ -4166,45 +4427,57 @@ 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(track.data_type()) < 1) { + error << _("Cannot write a range with no data.") << 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); - - for (uint32_t chan_n = 0; chan_n < diskstream_channels.n_audio(); ++chan_n) { + legal_playlist_name = legalize_for_path (playlist->name()); - 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; - } - } + for (uint32_t chan_n = 0; chan_n < diskstream_channels.n(track.data_type()); ++chan_n) { - 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 = ((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 ( - SourceFactory::createWritable (DataType::AUDIO, *this, buf, 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"), buf, 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 @@ -4213,45 +4486,92 @@ 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 >::iterator src = srcs.begin(); src != srcs.end(); ++src) { boost::shared_ptr afs = boost::dynamic_pointer_cast(*src); - if (afs) + boost::shared_ptr ms; + if (afs) { afs->prepare_for_peakfile_writes (); + } else if ((ms = boost::dynamic_pointer_cast(*src))) { + Source::Lock lock(ms->mutex()); + ms->mark_streaming_write_started(lock); + } } 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 >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) { boost::shared_ptr afs = boost::dynamic_pointer_cast(*src); + boost::shared_ptr ms; 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; } + } else if ((ms = boost::dynamic_pointer_cast(*src))) { + Source::Lock lock(ms->mutex()); + + const MidiBuffer& buf = buffers.get_midi(0); + for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) { + Evoral::Event ev = *i; + ev.set_time(ev.time() - position); + ms->append_event_frames(lock, ev, ms->timeline_position()); + } } } + 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 >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) { + boost::shared_ptr afs = boost::dynamic_pointer_cast(*src); + + if (afs) { + if (afs->write (buffers.get_audio(n).data(), this_chunk) != this_chunk) { + goto out; + } + } + } } if (!itt.cancel) { @@ -4263,10 +4583,14 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, for (vector >::iterator src=srcs.begin(); src != srcs.end(); ++src) { boost::shared_ptr afs = boost::dynamic_pointer_cast(*src); + boost::shared_ptr ms; if (afs) { afs->update_header (position, *xnow, now); afs->flush_header (); + } else if ((ms = boost::dynamic_pointer_cast(*src))) { + Source::Lock lock(ms->mutex()); + ms->mark_streaming_write_completed(lock); } } @@ -4285,12 +4609,7 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, out: if (!result) { for (vector >::iterator src = srcs.begin(); src != srcs.end(); ++src) { - boost::shared_ptr afs = boost::dynamic_pointer_cast(*src); - - if (afs) { - afs->mark_for_remove (); - } - + (*src)->mark_for_remove (); (*src)->drop_references (); } @@ -4303,8 +4622,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()); } @@ -4467,6 +4788,22 @@ Session::route_removed_from_route_group (RouteGroup* rg, boost::weak_ptr RouteRemovedFromRouteGroup (rg, r); } +boost::shared_ptr +Session::get_tracks () const +{ + boost::shared_ptr rl = routes.reader (); + boost::shared_ptr tl (new RouteList); + + for (RouteList::const_iterator r = rl->begin(); r != rl->end(); ++r) { + if (boost::dynamic_pointer_cast (*r)) { + if (!(*r)->is_auditioner()) { + tl->push_back (*r); + } + } + } + return tl; +} + boost::shared_ptr Session::get_routes_with_regions_at (framepos_t const p) const { @@ -4595,18 +4932,18 @@ Session::end_time_changed (framepos_t old) } } -string +std::vector Session::source_search_path (DataType type) const { - vector 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 { @@ -4614,10 +4951,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; } } @@ -4626,49 +4963,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 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::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::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 dirs; + Searchpath sp; if (path == ".") { return; @@ -4676,16 +4994,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::iterator i = dirs.begin(); i != dirs.end(); ++i) { + for (vector::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. @@ -4697,20 +5013,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: + 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 (search_path); + config.set_audio_search_path (sp.to_string()); break; case DataType::MIDI: - config.set_midi_search_path (search_path); + config.set_midi_search_path (sp.to_string()); break; } + } boost::shared_ptr @@ -4993,6 +5332,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");