X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession.cc;h=2aef7459615de38000acfcfe8c0090fbcd5e3dc1;hb=e36f74e071d4c14862d23da5ff0d49df0940d536;hp=428a5e52744b36e8df4e88472bceb8ff138faa92;hpb=8ddd12a60d08d895cb5400013cdda2e893fb81bb;p=ardour.git diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 428a5e5274..2aef745961 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -49,6 +49,7 @@ #include "ardour/amp.h" #include "ardour/analyser.h" +#include "ardour/async_midi_port.h" #include "ardour/audio_buffer.h" #include "ardour/audio_diskstream.h" #include "ardour/audio_port.h" @@ -66,6 +67,7 @@ #include "ardour/debug.h" #include "ardour/filename_extensions.h" #include "ardour/graph.h" +#include "ardour/midiport_manager.h" #include "ardour/midi_track.h" #include "ardour/midi_ui.h" #include "ardour/operations.h" @@ -85,12 +87,12 @@ #include "ardour/session_playlists.h" #include "ardour/smf_source.h" #include "ardour/source_factory.h" +#include "ardour/speakers.h" +#include "ardour/track.h" #include "ardour/utils.h" #include "midi++/port.h" -#include "midi++/jack_midi_port.h" #include "midi++/mmc.h" -#include "midi++/manager.h" #include "i18n.h" @@ -106,6 +108,7 @@ using namespace PBD; bool Session::_disable_all_loaded_plugins = false; +PBD::Signal1 Session::AudioEngineSetupRequired; PBD::Signal1 Session::Dialog; PBD::Signal0 Session::AskAboutPendingState; PBD::Signal2 Session::AskAboutSampleRateMismatch; @@ -130,70 +133,196 @@ Session::Session (AudioEngine &eng, const string& snapshot_name, BusProfile* bus_profile, string mix_template) - : _engine (eng) + : playlists (new SessionPlaylists) + , _engine (eng) + , process_function (&Session::process_with_events) + , waiting_for_sync_offset (false) + , _base_frame_rate (0) + , _current_frame_rate (0) + , _nominal_frame_rate (0) + , transport_sub_state (0) + , _record_status (Disabled) + , _transport_frame (0) + , _session_range_location (0) + , _slave (0) + , _silent (false) + , _transport_speed (0) + , _default_transport_speed (1.0) + , _last_transport_speed (0) , _target_transport_speed (0.0) + , auto_play_legal (false) + , _last_slave_transport_frame (0) + , maximum_output_latency (0) , _requested_return_frame (-1) + , current_block_size (0) + , _worst_output_latency (0) + , _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) + , _writable (false) + , _was_seamless (Config->get_seamless_loop ()) , _under_nsm_control (false) - , _session_dir (new SessionDirectory(fullpath)) + , delta_accumulator_cnt (0) + , average_slave_delta (1800) // !!! why 1800 ??? + , average_dir (0) + , have_first_delta_accumulator (false) + , _slave_state (Stopped) + , post_export_sync (false) + , post_export_position (0) + , _exporting (false) + , _export_started (false) + , _export_rolling (false) + , _pre_export_mmc_enabled (false) + , _name (snapshot_name) + , _is_new (true) + , _send_qf_mtc (false) + , _pframes_since_last_mtc (0) + , session_midi_feedback (0) + , play_loop (false) + , loop_changing (false) + , last_loopend (0) + , _session_dir (new SessionDirectory (fullpath)) + , _current_snapshot_name (snapshot_name) , state_tree (0) - , _state_of_the_state (Clean) + , state_was_pending (false) + , _state_of_the_state (StateOfTheState(CannotSave|InitialConnecting|Loading)) + , _last_roll_location (0) + , _last_roll_or_reversal_location (0) + , _last_record_location (0) + , pending_locate_roll (false) + , pending_locate_frame (0) + , pending_locate_flush (false) + , pending_abort (false) + , pending_auto_loop (false) , _butler (new Butler (*this)) , _post_transport_work (0) + , cumulative_rf_motion (0) + , rf_scale (1.0) + , _locations (new Locations (*this)) + , step_speed (0) + , outbound_mtc_timecode_frame (0) + , next_quarter_frame_to_send (-1) + , _frames_per_timecode_frame (0) + , _frames_per_hour (0) + , _timecode_frames_per_hour (0) + , last_timecode_valid (false) + , last_timecode_when (0) , _send_timecode_update (false) + , ltc_encoder (0) , ltc_enc_buf(0) + , ltc_buf_off (0) + , ltc_buf_len (0) + , ltc_speed (0) + , ltc_enc_byte (0) + , ltc_enc_pos (0) + , ltc_enc_cnt (0) + , ltc_enc_off (0) + , restarting (false) + , ltc_prev_cycle (0) + , ltc_timecode_offset (0) + , ltc_timecode_negative_offset (false) + , midi_control_ui (0) + , _tempo_map (0) , _all_route_group (new RouteGroup (*this, "all")) , routes (new RouteList) + , _adding_routes_in_progress (false) + , destructive_index (0) + , solo_update_disabled (false) + , default_fade_steepness (0) + , default_fade_msecs (0) , _total_free_4k_blocks (0) , _total_free_4k_blocks_uncertain (false) + , no_questions_about_missing_files (false) + , _playback_load (0) + , _capture_load (0) , _bundles (new BundleList) , _bundle_xml_node (0) , _current_trans (0) + , _clicking (false) , click_data (0) , click_emphasis_data (0) + , click_length (0) + , click_emphasis_length (0) + , _clicks_cleared (0) + , _play_range (false) , main_outs (0) + , first_file_data_format_reset (true) + , first_file_header_format_reset (true) + , have_looped (false) , _have_rec_enabled_track (false) + , _step_editors (0) , _suspend_timecode_transmission (0) + , _speakers (new Speakers) + , _order_hint (0) + , ignore_route_processor_changes (false) + , _midi_ports (0) + , _mmc (0) { - _locations = new Locations (*this); - ltc_encoder = NULL; + uint32_t sr = 0; - if (how_many_dsp_threads () > 1) { - /* For now, only create the graph if we are using >1 DSP threads, as - it is a bit slower than the old code with 1 thread. - */ - _process_graph.reset (new Graph (*this)); - } - - playlists.reset (new SessionPlaylists); + pre_engine_init (fullpath); + + if (_is_new) { + if (ensure_engine (sr)) { + destroy (); + throw failed_constructor (); + } - _all_route_group->set_active (true, this); + if (create (mix_template, bus_profile)) { + destroy (); + throw failed_constructor (); + } - interpolation.add_channel_to (0, 0); + /* if a mix template was provided, then ::create() will + * have copied it into the session and we need to load it + * so that we have the state ready for ::set_state() + * after the engine is started. + * + * Note that we do NOT try to get the sample rate from + * the template at this time, though doing so would + * be easy if we decided this was an appropriate part + * of a template. + */ - if (!eng.connected()) { - throw failed_constructor(); - } + if (!mix_template.empty() && load_state (_current_snapshot_name)) { + throw failed_constructor (); + } - n_physical_outputs = _engine.n_physical_outputs (); - n_physical_inputs = _engine.n_physical_inputs (); + } else { - first_stage_init (fullpath, snapshot_name); + if (load_state (_current_snapshot_name)) { + throw failed_constructor (); + } + + /* try to get sample rate from XML state so that we + * can influence the SR if we set up the audio + * engine. + */ - _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)); + if (state_tree) { + const XMLProperty* prop; + if ((prop = state_tree->root()->property (X_("sample-rate"))) != 0) { + sr = atoi (prop->value()); + } + } - if (_is_new) { - if (create (mix_template, bus_profile)) { + if (ensure_engine (sr)) { destroy (); throw failed_constructor (); } } - if (second_stage_init ()) { + if (post_engine_init ()) { destroy (); throw failed_constructor (); } - store_recent_sessions(_name, _path); + store_recent_sessions (_name, _path); bool was_dirty = dirty(); @@ -210,6 +339,16 @@ Session::Session (AudioEngine &eng, EndTimeChanged.connect_same_thread (*this, boost::bind (&Session::end_time_changed, this, _1)); _is_new = false; + + /* hook us up to the engine since we are now completely constructed */ + + BootMessage (_("Connect to engine")); + + _engine.set_session (this); + _engine.reset_timebase (); + + BootMessage (_("Session loading complete")); + } Session::~Session () @@ -220,6 +359,81 @@ Session::~Session () destroy (); } +int +Session::ensure_engine (uint32_t desired_sample_rate) +{ + if (_engine.current_backend() == 0) { + /* backend is unknown ... */ + boost::optional r = AudioEngineSetupRequired (desired_sample_rate); + if (r.get_value_or (-1) != 0) { + return -1; + } + } else if (_engine.setup_required()) { + /* backend is known, but setup is needed */ + boost::optional r = AudioEngineSetupRequired (desired_sample_rate); + if (r.get_value_or (-1) != 0) { + return -1; + } + } else if (!_engine.running()) { + if (_engine.start()) { + return -1; + } + } + + /* at this point the engine should be running + */ + + if (!_engine.running()) { + return -1; + } + + return immediately_post_engine (); + +} + +int +Session::immediately_post_engine () +{ + /* Do various initializations that should take place directly after we + * know that the engine is running, but before we either create a + * session or set state for an existing one. + */ + + if (how_many_dsp_threads () > 1) { + /* For now, only create the graph if we are using >1 DSP threads, as + it is a bit slower than the old code with 1 thread. + */ + _process_graph.reset (new Graph (*this)); + } + + /* every time we reconnect, recompute worst case output latencies */ + + _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this)); + + if (synced_to_engine()) { + _engine.transport_stop (); + } + + if (config.get_jack_time_master()) { + _engine.transport_locate (_transport_frame); + } + + try { + BootMessage (_("Set up LTC")); + setup_ltc (); + BootMessage (_("Set up Click")); + setup_click (); + BootMessage (_("Set up standard connections")); + setup_bundles (); + } + + catch (failed_constructor& err) { + return -1; + } + + return 0; +} + void Session::destroy () { @@ -313,13 +527,16 @@ 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 (); - } + { + DEBUG_TRACE (DEBUG::Destruction, "delete sources\n"); + Glib::Threads::Mutex::Lock lm (source_lock); + for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) { + DEBUG_TRACE(DEBUG::Destruction, string_compose ("Dropping for source %1 ; pre-ref = %2\n", i->second->name(), i->second.use_count())); + i->second->drop_references (); + } - sources.clear (); + sources.clear (); + } DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n"); for (list::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) { @@ -330,7 +547,9 @@ Session::destroy () /* not strictly necessary, but doing it here allows the shared_ptr debugging to work */ playlists.reset (); - delete _locations; + delete _mmc; _mmc = 0; + delete _midi_ports; _midi_ports = 0; + delete _locations; _locations = 0; DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n"); @@ -340,135 +559,112 @@ Session::destroy () } void -Session::when_engine_running () +Session::setup_ltc () { - string first_physical_output; - - BootMessage (_("Set block size and sample rate")); - - set_block_size (_engine.frames_per_cycle()); - set_frame_rate (_engine.frame_rate()); - - BootMessage (_("Using configuration")); - - boost::function ff (boost::bind (&Session::config_changed, this, _1, false)); - boost::function ft (boost::bind (&Session::config_changed, this, _1, true)); - - Config->map_parameters (ff); - config.map_parameters (ft); - - /* every time we reconnect, recompute worst case output latencies */ - - _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this)); - - if (synced_to_jack()) { - _engine.transport_stop (); + XMLNode* child = 0; + + _ltc_input.reset (new IO (*this, X_("LTC In"), IO::Input)); + _ltc_output.reset (new IO (*this, X_("LTC Out"), IO::Output)); + + if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC In"))) != 0) { + _ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version); + } else { + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + _ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); + } + reconnect_ltc_input (); } - - if (config.get_jack_time_master()) { - _engine.transport_locate (_transport_frame); + + if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC Out"))) != 0) { + _ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version); + } else { + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + _ltc_output->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); + } + reconnect_ltc_output (); } + + /* fix up names of LTC ports because we don't want the normal + * IO style of NAME/TYPE-{in,out}N + */ + + _ltc_input->nth (0)->set_name (X_("LTC-in")); + _ltc_output->nth (0)->set_name (X_("LTC-out")); +} +void +Session::setup_click () +{ _clicking = false; + _click_io.reset (new ClickIO (*this, X_("Click"))); + _click_gain.reset (new Amp (*this)); + _click_gain->activate (); + if (state_tree) { + setup_click_state (state_tree->root()); + } else { + setup_click_state (0); + } +} - try { - XMLNode* child = 0; +void +Session::setup_click_state (const XMLNode* node) +{ + const XMLNode* child = 0; + + if (node && (child = find_named_node (*node, "Click")) != 0) { - _ltc_input.reset (new IO (*this, _("LTC In"), IO::Input)); - _ltc_output.reset (new IO (*this, _("LTC Out"), IO::Output)); + /* existing state for Click */ + int c = 0; - if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-In")) != 0) { - _ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version); + if (Stateful::loading_state_version < 3000) { + c = _click_io->set_state_2X (*child->children().front(), Stateful::loading_state_version, false); } else { - { - Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - _ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); - } - reconnect_ltc_input (); - } - - if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-Out")) != 0) { - _ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version); - } else { - { - Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - _ltc_output->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); - } - reconnect_ltc_output (); - } - - /* fix up names of LTC ports because we don't want the normal - * IO style of NAME/TYPE-{in,out}N - */ - - _ltc_input->nth (0)->set_name (_("LTC-in")); - _ltc_output->nth (0)->set_name (_("LTC-out")); - - _click_io.reset (new ClickIO (*this, "click")); - _click_gain.reset (new Amp (*this)); - _click_gain->activate (); - - if (state_tree && (child = find_named_node (*state_tree->root(), "Click")) != 0) { - - /* existing state for Click */ - int c = 0; - - if (Stateful::loading_state_version < 3000) { - c = _click_io->set_state_2X (*child->children().front(), Stateful::loading_state_version, false); - } else { - const XMLNodeList& children (child->children()); - XMLNodeList::const_iterator i = children.begin(); - if ((c = _click_io->set_state (**i, Stateful::loading_state_version)) == 0) { - ++i; - if (i != children.end()) { - c = _click_gain->set_state (**i, Stateful::loading_state_version); - } + const XMLNodeList& children (child->children()); + XMLNodeList::const_iterator i = children.begin(); + if ((c = _click_io->set_state (**i, Stateful::loading_state_version)) == 0) { + ++i; + if (i != children.end()) { + c = _click_gain->set_state (**i, Stateful::loading_state_version); } } + } - if (c == 0) { - _clicking = Config->get_clicking (); + if (c == 0) { + _clicking = Config->get_clicking (); - } else { + } else { - error << _("could not setup Click I/O") << endmsg; - _clicking = false; - } + error << _("could not setup Click I/O") << endmsg; + _clicking = false; + } - } else { + } else { - /* default state for Click: dual-mono to first 2 physical outputs */ + /* default state for Click: dual-mono to first 2 physical outputs */ - vector outs; - _engine.get_physical_outputs (DataType::AUDIO, outs); + vector outs; + _engine.get_physical_outputs (DataType::AUDIO, outs); - for (uint32_t physport = 0; physport < 2; ++physport) { - if (outs.size() > physport) { - if (_click_io->add_port (outs[physport], this)) { - // relax, even though its an error - } + for (uint32_t physport = 0; physport < 2; ++physport) { + if (outs.size() > physport) { + if (_click_io->add_port (outs[physport], this)) { + // relax, even though its an error } } - - if (_click_io->n_ports () > ChanCount::ZERO) { - _clicking = Config->get_clicking (); - } } - } - catch (failed_constructor& err) { - error << _("cannot setup Click I/O") << endmsg; - } - - BootMessage (_("Compute I/O Latencies")); - - if (_clicking) { - // XXX HOW TO ALERT UI TO THIS ? DO WE NEED TO? + if (_click_io->n_ports () > ChanCount::ZERO) { + _clicking = Config->get_clicking (); + } } +} - BootMessage (_("Set up standard connections")); - +void +Session::setup_bundles () +{ vector inputs[DataType::num_types]; vector outputs[DataType::num_types]; for (uint32_t i = 0; i < DataType::num_types; ++i) { @@ -567,53 +763,6 @@ Session::when_engine_running () add_bundle (c); } - BootMessage (_("Setup signal flow and plugins")); - - /* Reset all panners */ - - Delivery::reset_panners (); - - /* this will cause the CPM to instantiate any protocols that are in use - * (or mandatory), which will pass it this Session, and then call - * set_state() on each instantiated protocol to match stored state. - */ - - ControlProtocolManager::instance().set_session (this); - - /* This must be done after the ControlProtocolManager set_session above, - as it will set states for ports which the ControlProtocolManager creates. - */ - - MIDI::Manager::instance()->set_port_states (Config->midi_port_states ()); - - /* And this must be done after the MIDI::Manager::set_port_states as - * it will try to make connections whose details are loaded by set_port_states. - */ - - hookup_io (); - - /* Let control protocols know that we are now all connected, so they - * could start talking to surfaces if they want to. - */ - - ControlProtocolManager::instance().midi_connectivity_established (); - - if (_is_new && !no_auto_connect()) { - Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock()); - auto_connect_master_bus (); - } - - _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty)); - - /* update latencies */ - - initialize_latencies (); - - /* hook us up to the engine */ - - BootMessage (_("Connect to engine")); - _engine.set_session (this); - _engine.reset_timebase (); } void @@ -660,6 +809,12 @@ Session::remove_monitor_section () /* force reversion to Solo-In-Place */ Config->set_solo_control_is_listen_control (false); + /* if we are auditioning, cancel it ... this is a workaround + to a problem (auditioning does not execute the process graph, + which is needed to remove routes when using >1 core for processing) + */ + cancel_audition (); + { /* Hold process lock while doing this so that we don't hear bits and * pieces of audio as we work on each route. @@ -690,6 +845,10 @@ Session::remove_monitor_section () remove_route (_monitor_out); auto_connect_master_bus (); + + if (auditioner) { + auditioner->connect (); + } } void @@ -775,14 +934,14 @@ Session::add_monitor_section () /* Monitor bus is audio only */ - uint32_t mod = n_physical_outputs.get (DataType::AUDIO); - uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO); vector outputs[DataType::num_types]; for (uint32_t i = 0; i < DataType::num_types; ++i) { _engine.get_physical_outputs (DataType (DataType::Symbol (i)), outputs[i]); } - + + uint32_t mod = outputs[DataType::AUDIO].size(); + uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO); if (mod != 0) { @@ -834,6 +993,10 @@ Session::add_monitor_section () (*x)->enable_monitor_send (); } } + + if (auditioner) { + auditioner->connect (); + } } void @@ -874,7 +1037,12 @@ Session::hookup_io () /* Tell all IO objects to connect themselves together */ IO::enable_connecting (); - MIDI::JackMIDIPort::MakeConnections (); + + /* Now tell all "floating" ports to connect to whatever + they should be connected to. + */ + + AudioEngine::instance()->reconnect_ports (); /* Anyone who cares about input state, wake up and do something */ @@ -936,7 +1104,7 @@ Session::set_track_monitor_input_status (bool yn) boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); if (tr && tr->record_enabled ()) { //cerr << "switching to input = " << !auto_input << __FILE__ << __LINE__ << endl << endl; - tr->request_jack_monitors_input (yn); + tr->request_input_monitoring (yn); } } } @@ -1169,7 +1337,7 @@ Session::enable_record () if (g_atomic_int_compare_and_exchange (&_record_status, rs, Recording)) { _last_record_location = _transport_frame; - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe)); if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) { set_track_monitor_input_status (true); @@ -1190,7 +1358,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); - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit)); } else { if (rs == Recording) { g_atomic_int_set (&_record_status, Enabled); @@ -1244,7 +1412,7 @@ Session::maybe_enable_record () enable_record (); } } else { - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause)); RecordStateChanged (); /* EMIT SIGNAL */ } @@ -1281,7 +1449,7 @@ Session::audible_frame () const offset = current_block_size; } - if (synced_to_jack()) { + if (synced_to_engine()) { tf = _engine.transport_frame(); } else { tf = _transport_frame; @@ -1341,6 +1509,7 @@ Session::set_frame_rate (framecnt_t frames_per_second) */ _base_frame_rate = frames_per_second; + _nominal_frame_rate = frames_per_second; sync_time_vars(); @@ -1549,7 +1718,7 @@ Session::resort_routes_using (boost::shared_ptr r) DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n"); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 signal order %2\n", - (*i)->name(), (*i)->order_key (MixerSort))); + (*i)->name(), (*i)->order_key ())); } #endif @@ -1834,9 +2003,9 @@ Session::auto_connect_route (boost::shared_ptr route, ChanCount& existing for (uint32_t i = output_start.get(*t); i < route->n_outputs().get(*t); ++i) { string port; - if ((*t) == DataType::MIDI || Config->get_output_auto_connect() & AutoConnectPhysical) { + if ((*t) == DataType::MIDI && (Config->get_output_auto_connect() & AutoConnectPhysical)) { port = physoutputs[(out_offset.get(*t) + i) % nphysical_out]; - } else if ((*t) == DataType::AUDIO && Config->get_output_auto_connect() & AutoConnectMaster) { + } else if ((*t) == DataType::AUDIO && (Config->get_output_auto_connect() & AutoConnectMaster)) { /* master bus is audio only */ if (_master_out && _master_out->n_inputs().get(*t) > 0) { port = _master_out->input()->ports().port(*t, @@ -2185,6 +2354,11 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool ChanCount existing_outputs; uint32_t order = next_control_id(); + if (_order_hint != 0) { + order = _order_hint; + _order_hint = 0; + } + count_existing_track_channels (existing_inputs, existing_outputs); { @@ -2246,15 +2420,13 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool ID in most situations. */ - if (!r->has_order_key (EditorSort)) { + if (!r->has_order_key ()) { if (r->is_auditioner()) { /* use an arbitrarily high value */ - r->set_order_key (EditorSort, UINT_MAX); - r->set_order_key (MixerSort, UINT_MAX); + r->set_order_key (UINT_MAX); } else { DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("while adding, set %1 to order key %2\n", r->name(), order)); - r->set_order_key (EditorSort, order); - r->set_order_key (MixerSort, order); + r->set_order_key (order); order++; } } @@ -3132,12 +3304,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::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); @@ -3145,7 +3321,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::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 @@ -3166,111 +3366,6 @@ 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) -{ - 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 - - the task here is to replace NAME with the new name. - */ - - string dir; - string suffix; - string::size_type dash; - string::size_type postfix; - - 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 ""; - } - - 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 (postfix != string::npos) { - suffix = suffix.substr (postfix); - } else { - error << "Logic error in Session::change_source_path_by_name(), please report" << endl; - return ""; - } - - const uint32_t limit = 10000; - char buf[PATH_MAX+1]; - - for (uint32_t cnt = 1; cnt <= limit; ++cnt) { - - snprintf (buf, sizeof(buf), "%s-%u%s", newname.c_str(), cnt, suffix.c_str()); - - if (!matching_unsuffixed_filename_exists_in (dir, buf)) { - path = Glib::build_filename (dir, buf); - break; - } - - path = ""; - } - - if (path.empty()) { - fatal << string_compose (_("FATAL ERROR! Could not find a suitable version of %1 for a rename"), - newname) << endl; - /*NOTREACHED*/ - } - } - - return path; -} - /** 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) @@ -3374,6 +3469,22 @@ Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t cha 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, buf); + + if (source_by_path (possible_path)) { + existing++; + break; + } } if (existing == 0) { @@ -3403,19 +3514,21 @@ Session::create_audio_source_for_session (size_t n_chans, string const & n, uint SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate())); } -/** Return a unique name based on \a base for a new internal MIDI source */ +/** Return a unique name based on \a owner_name for a new internal MIDI source */ string -Session::new_midi_source_name (const string& base) +Session::new_midi_source_name (const string& owner_name) { uint32_t cnt; char buf[PATH_MAX+1]; const uint32_t limit = 10000; string legalized; + string possible_name; buf[0] = '\0'; - legalized = legalize_for_path (base); + legalized = legalize_for_path (owner_name); // Find a "version" of the file name that doesn't exist in any of the possible directories. + for (cnt = 1; cnt <= limit; ++cnt) { vector::iterator i; @@ -3424,12 +3537,17 @@ Session::new_midi_source_name (const string& base) for (i = session_dirs.begin(); i != session_dirs.end(); ++i) { SessionDirectory sdir((*i).path); + + snprintf (buf, sizeof(buf), "%s-%u.mid", legalized.c_str(), cnt); + possible_name = buf; - std::string p = Glib::build_filename (sdir.midi_path(), legalized); - - snprintf (buf, sizeof(buf), "%s-%u.mid", p.c_str(), cnt); + std::string possible_path = Glib::build_filename (sdir.midi_path(), possible_name); + + if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) { + existing++; + } - if (Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) { + if (source_by_path (possible_path)) { existing++; } } @@ -3441,37 +3559,64 @@ Session::new_midi_source_name (const string& base) if (cnt > limit) { error << string_compose( _("There are already %1 recordings for %2, which I consider too many."), - limit, base) << endmsg; + limit, owner_name) << endmsg; destroy (); throw failed_constructor(); } } - return Glib::path_get_basename(buf); + return possible_name; } /** 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 - */ + std::string name; - if (track) { - /*MidiTrack* mt = dynamic_cast (track); - assert (mt); - */ + if (name.empty()) { + name = new_midi_source_name (basic_name); + } - list > l = track->steal_write_sources (); + const string path = new_source_path_from_name (DataType::MIDI, name); - if (!l.empty()) { - assert (boost::dynamic_pointer_cast (l.front())); - return boost::dynamic_pointer_cast (l.front()); - } + return boost::dynamic_pointer_cast ( + SourceFactory::createWritable ( + DataType::MIDI, *this, path, false, frame_rate())); +} + +/** 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 (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); return boost::dynamic_pointer_cast ( @@ -3565,7 +3710,7 @@ Session::RoutePublicOrderSorter::operator() (boost::shared_ptr a, boost:: if (b->is_monitor()) { return false; } - return a->order_key (MixerSort) < b->order_key (MixerSort); + return a->order_key () < b->order_key (); } bool @@ -4777,8 +4922,7 @@ Session::notify_remote_id_change () } switch (Config->get_remote_model()) { - case MixerSort: - case EditorSort: + case MixerOrdered: Route::RemoteControlIDChange (); /* EMIT SIGNAL */ break; default: @@ -4787,7 +4931,7 @@ Session::notify_remote_id_change () } void -Session::sync_order_keys (RouteSortOrderKey sort_key_changed) +Session::sync_order_keys () { if (deletion_in_progress()) { return; @@ -4799,9 +4943,9 @@ Session::sync_order_keys (RouteSortOrderKey sort_key_changed) opportunity to keep them in sync if they wish to. */ - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("Sync Order Keys, based on %1\n", enum_2_string (sort_key_changed))); + DEBUG_TRACE (DEBUG::OrderKeys, "Sync Order Keys.\n"); - Route::SyncOrderKeys (sort_key_changed); /* EMIT SIGNAL */ + Route::SyncOrderKeys (); /* EMIT SIGNAL */ DEBUG_TRACE (DEBUG::OrderKeys, "\tsync done\n"); }