X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession.cc;h=c335e021aa616e7294ac25542db15af52c68aa2c;hb=7b818e9a7f3d999eb6bcc90c961d2b42531c3917;hp=923fd6164f332cd76799666fc18d555f32e10c89;hpb=a473d630eb165272992e90f8d854b1d66ec0be63;p=ardour.git diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 923fd6164f..c335e021aa 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include @@ -45,6 +45,7 @@ #include "pbd/file_utils.h" #include "pbd/convert.h" #include "pbd/strsplit.h" +#include "pbd/unwind.h" #include "ardour/amp.h" #include "ardour/analyser.h" @@ -54,61 +55,51 @@ #include "ardour/audio_track.h" #include "ardour/audioengine.h" #include "ardour/audiofilesource.h" -#include "ardour/audioplaylist.h" -#include "ardour/audioregion.h" #include "ardour/auditioner.h" #include "ardour/buffer_manager.h" #include "ardour/buffer_set.h" #include "ardour/bundle.h" #include "ardour/butler.h" #include "ardour/click.h" -#include "ardour/configuration.h" #include "ardour/control_protocol_manager.h" -#include "ardour/crossfade.h" -#include "ardour/cycle_timer.h" #include "ardour/data_type.h" #include "ardour/debug.h" #include "ardour/filename_extensions.h" -#include "ardour/internal_send.h" -#include "ardour/io_processor.h" -#include "ardour/midi_diskstream.h" -#include "ardour/midi_playlist.h" -#include "ardour/midi_region.h" +#include "ardour/graph.h" #include "ardour/midi_track.h" #include "ardour/midi_ui.h" -#include "ardour/named_selection.h" -#include "ardour/process_thread.h" +#include "ardour/operations.h" #include "ardour/playlist.h" +#include "ardour/plugin.h" #include "ardour/plugin_insert.h" -#include "ardour/port_insert.h" -#include "ardour/processor.h" +#include "ardour/process_thread.h" #include "ardour/rc_configuration.h" #include "ardour/recent_sessions.h" +#include "ardour/region.h" #include "ardour/region_factory.h" -#include "ardour/return.h" +#include "ardour/route_graph.h" #include "ardour/route_group.h" #include "ardour/send.h" #include "ardour/session.h" #include "ardour/session_directory.h" -#include "ardour/session_directory.h" -#include "ardour/session_metadata.h" #include "ardour/session_playlists.h" -#include "ardour/slave.h" #include "ardour/smf_source.h" #include "ardour/source_factory.h" -#include "ardour/tape_file_matcher.h" -#include "ardour/tempo.h" #include "ardour/utils.h" -#include "ardour/graph.h" -#include "ardour/speakers.h" -#include "ardour/operations.h" #include "midi++/port.h" +#include "midi++/jack_midi_port.h" #include "midi++/mmc.h" #include "midi++/manager.h" #include "i18n.h" +namespace ARDOUR { +class MidiSource; +class Processor; +class Speakers; +} + using namespace std; using namespace ARDOUR; using namespace PBD; @@ -123,15 +114,16 @@ PBD::Signal3 Session::MissingFile; PBD::Signal1 Session::StartTimeChanged; PBD::Signal1 Session::EndTimeChanged; -PBD::Signal0 Session::AutoBindingOn; -PBD::Signal0 Session::AutoBindingOff; PBD::Signal2 Session::Exported; PBD::Signal1 > Session::AskAboutPlaylistDeletion; PBD::Signal0 Session::Quit; +PBD::Signal0 Session::FeedbackDetected; +PBD::Signal0 Session::SuccessfulGraphSort; static void clean_up_session_event (SessionEvent* ev) { delete ev; } const SessionEvent::RTeventCallback Session::rt_cleanup (clean_up_session_event); +/** @param snapshot_name Snapshot name, without .ardour suffix */ Session::Session (AudioEngine &eng, const string& fullpath, const string& snapshot_name, @@ -147,21 +139,29 @@ Session::Session (AudioEngine &eng, , _post_transport_work (0) , _send_timecode_update (false) , _all_route_group (new RouteGroup (*this, "all")) - , route_graph (new Graph(*this)) , routes (new RouteList) , _total_free_4k_blocks (0) + , _total_free_4k_blocks_uncertain (false) , _bundles (new BundleList) , _bundle_xml_node (0) , _current_trans (0) - , _click_io ((IO*) 0) , click_data (0) , click_emphasis_data (0) , main_outs (0) - , _metadata (new SessionMetadata()) , _have_rec_enabled_track (false) , _suspend_timecode_transmission (0) { _locations = new Locations (*this); +#ifdef HAVE_LTC + ltc_encoder = NULL; +#endif + + 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); @@ -213,6 +213,9 @@ Session::Session (AudioEngine &eng, Session::~Session () { +#ifdef PT_TIMING + ST.dump ("ST.dump"); +#endif destroy (); } @@ -231,6 +234,16 @@ Session::destroy () _engine.remove_session (); + /* deregister all ports - there will be no process or any other + * callbacks from the engine any more. + */ + + Port::PortDrop (); /* EMIT SIGNAL */ + +#ifdef HAVE_LTC + ltc_tx_cleanup(); +#endif + /* clear history so that no references to objects are held any more */ _history.clear (); @@ -245,6 +258,8 @@ Session::destroy () _butler->drop_references (); delete _butler; + _butler = 0; + delete midi_control_ui; delete _all_route_group; @@ -270,9 +285,6 @@ Session::destroy () /* tell everyone to drop references and delete objects as we go */ - DEBUG_TRACE (DEBUG::Destruction, "delete named selections\n"); - named_selections.clear (); - DEBUG_TRACE (DEBUG::Destruction, "delete regions\n"); RegionFactory::delete_all_regions (); @@ -312,8 +324,6 @@ Session::destroy () delete *i; } - Crossfade::set_buffer_size (0); - /* not strictly necessary, but doing it here allows the shared_ptr debugging to work */ playlists.reset (); @@ -346,7 +356,7 @@ Session::when_engine_running () /* every time we reconnect, recompute worst case output latencies */ - _engine.Running.connect_same_thread (*this, boost::bind (&Session::set_worst_io_latencies, this)); + _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this)); if (synced_to_jack()) { _engine.transport_stop (); @@ -360,21 +370,52 @@ Session::when_engine_running () try { XMLNode* child = 0; + + _ltc_input.reset (new IO (*this, _("LTC In"), IO::Input)); + _ltc_output.reset (new IO (*this, _("LTC Out"), IO::Output)); + + if (state_tree && (child = find_named_node (*state_tree->root(), "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 (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 (); + } + _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; + int c = 0; if (Stateful::loading_state_version < 3000) { c = _click_io->set_state_2X (*child->children().front(), Stateful::loading_state_version, false); } else { - c = _click_io->set_state (*child->children().front(), 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 (); @@ -518,11 +559,21 @@ Session::when_engine_running () 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 @@ -531,106 +582,215 @@ Session::when_engine_running () hookup_io (); - if (_is_new && !no_auto_connect()) { + /* Let control protocols know that we are now all connected, so they + * could start talking to surfaces if they want to. + */ - Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock()); + ControlProtocolManager::instance().midi_connectivity_established (); - /* don't connect the master bus outputs if there is a monitor bus */ + if (_is_new && !no_auto_connect()) { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock()); + auto_connect_master_bus (); + } - if (_master_out && Config->get_auto_connect_standard_busses() && !_monitor_out) { + _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty)); - /* if requested auto-connect the outputs to the first N physical ports. - */ + /* update latencies */ - uint32_t limit = _master_out->n_outputs().n_total(); + initialize_latencies (); - for (uint32_t n = 0; n < limit; ++n) { - Port* p = _master_out->output()->nth (n); - string connect_to; - if (outputs[p->type()].size() > n) { - connect_to = outputs[p->type()][n]; - } + /* hook us up to the engine */ - if (!connect_to.empty() && p->connected_to (connect_to) == false) { - if (_master_out->output()->connect (p, connect_to, this)) { - error << string_compose (_("cannot connect master output %1 to %2"), n, connect_to) - << endmsg; - break; - } - } + BootMessage (_("Connect to engine")); + _engine.set_session (this); + _engine.reset_timebase (); +} + +void +Session::auto_connect_master_bus () +{ + if (!_master_out || !Config->get_auto_connect_standard_busses() || _monitor_out) { + return; + } + + /* if requested auto-connect the outputs to the first N physical ports. + */ + + uint32_t limit = _master_out->n_outputs().n_total(); + 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]); + } + + for (uint32_t n = 0; n < limit; ++n) { + boost::shared_ptr p = _master_out->output()->nth (n); + string connect_to; + if (outputs[p->type()].size() > n) { + connect_to = outputs[p->type()][n]; + } + + if (!connect_to.empty() && p->connected_to (connect_to) == false) { + if (_master_out->output()->connect (p, connect_to, this)) { + error << string_compose (_("cannot connect master output %1 to %2"), n, connect_to) + << endmsg; + break; } } + } +} - if (_monitor_out) { +void +Session::remove_monitor_section () +{ + if (!_monitor_out) { + return; + } - /* AUDIO ONLY as of june 29th 2009, because listen semantics for anything else - are undefined, at best. - */ + /* force reversion to Solo-In-Place */ + Config->set_solo_control_is_listen_control (false); - /* control out listens to master bus (but ignores it - under some conditions) - */ + { + /* Hold process lock while doing this so that we don't hear bits and + * pieces of audio as we work on each route. + */ + + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + + /* Connect tracks to monitor section. Note that in an + existing session, the internal sends will already exist, but we want the + routes to notice that they connect to the control out specifically. + */ + + + boost::shared_ptr r = routes.reader (); + PBD::Unwinder uw (ignore_route_processor_changes, true); + + for (RouteList::iterator x = r->begin(); x != r->end(); ++x) { + + if ((*x)->is_monitor()) { + /* relax */ + } else if ((*x)->is_master()) { + /* relax */ + } else { + (*x)->remove_aux_or_listen (_monitor_out); + } + } + } - uint32_t limit = _monitor_out->n_inputs().n_audio(); + remove_route (_monitor_out); + auto_connect_master_bus (); +} - if (_master_out) { - for (uint32_t n = 0; n < limit; ++n) { - AudioPort* p = _monitor_out->input()->ports().nth_audio_port (n); - AudioPort* o = _master_out->output()->ports().nth_audio_port (n); +void +Session::add_monitor_section () +{ + RouteList rl; - if (o) { - string connect_to = o->name(); - if (_monitor_out->input()->connect (p, connect_to, this)) { - error << string_compose (_("cannot connect control input %1 to %2"), n, connect_to) - << endmsg; - break; - } - } - } - } + if (_monitor_out || !_master_out) { + return; + } - /* if control out is not connected, connect control out to physical outs - */ + boost::shared_ptr r (new Route (*this, _("monitor"), Route::MonitorOut, DataType::AUDIO)); - if (!_monitor_out->output()->connected ()) { + if (r->init ()) { + return; + } - if (!Config->get_monitor_bus_preferred_bundle().empty()) { +#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS + // boost_debug_shared_ptr_mark_interesting (r.get(), "Route"); +#endif + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + r->input()->ensure_io (_master_out->output()->n_ports(), false, this); + r->output()->ensure_io (_master_out->output()->n_ports(), false, this); + } - boost::shared_ptr b = bundle_by_name (Config->get_monitor_bus_preferred_bundle()); + rl.push_back (r); + add_routes (rl, false, false, false); + + assert (_monitor_out); - if (b) { - _monitor_out->output()->connect_ports_to_bundle (b, this); - } else { - warning << string_compose (_("The preferred I/O for the monitor bus (%1) cannot be found"), - Config->get_monitor_bus_preferred_bundle()) - << endmsg; - } + /* AUDIO ONLY as of june 29th 2009, because listen semantics for anything else + are undefined, at best. + */ + + uint32_t limit = _monitor_out->n_inputs().n_audio(); + + if (_master_out) { + + /* connect the inputs to the master bus outputs. this + * represents a separate data feed from the internal sends from + * each route. as of jan 2011, it allows the monitor section to + * conditionally ignore either the internal sends or the normal + * input feed, but we should really find a better way to do + * this, i think. + */ + + _master_out->output()->disconnect (this); + + for (uint32_t n = 0; n < limit; ++n) { + boost::shared_ptr p = _monitor_out->input()->ports().nth_audio_port (n); + boost::shared_ptr o = _master_out->output()->ports().nth_audio_port (n); + + if (o) { + string connect_to = o->name(); + if (_monitor_out->input()->connect (p, connect_to, this)) { + error << string_compose (_("cannot connect control input %1 to %2"), n, connect_to) + << endmsg; + break; + } + } + } + } + + /* if monitor section is not connected, connect it to physical outs + */ + + if (Config->get_auto_connect_standard_busses() && !_monitor_out->output()->connected ()) { + + if (!Config->get_monitor_bus_preferred_bundle().empty()) { + + boost::shared_ptr b = bundle_by_name (Config->get_monitor_bus_preferred_bundle()); + + if (b) { + _monitor_out->output()->connect_ports_to_bundle (b, true, this); + } else { + warning << string_compose (_("The preferred I/O for the monitor bus (%1) cannot be found"), + Config->get_monitor_bus_preferred_bundle()) + << endmsg; + } + + } else { + + /* Monitor bus is audio only */ - } else { + uint32_t mod = n_physical_outputs.get (DataType::AUDIO); + uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO); + vector outputs[DataType::num_types]; - /* Monitor bus is audio only */ - uint32_t mod = n_physical_outputs.get (DataType::AUDIO); - uint32_t limit = _monitor_out->n_outputs().get (DataType::AUDIO); - - if (mod != 0) { - - for (uint32_t n = 0; n < limit; ++n) { - - Port* p = _monitor_out->output()->ports().port(DataType::AUDIO, n); - string connect_to; - if (outputs[DataType::AUDIO].size() > (n % mod)) { - connect_to = outputs[DataType::AUDIO][n % mod]; - } - - if (!connect_to.empty()) { - if (_monitor_out->output()->connect (p, connect_to, this)) { - error << string_compose ( - _("cannot connect control output %1 to %2"), - n, connect_to) - << endmsg; - break; - } - } + for (uint32_t i = 0; i < DataType::num_types; ++i) { + _engine.get_physical_outputs (DataType (DataType::Symbol (i)), outputs[i]); + } + + + if (mod != 0) { + + for (uint32_t n = 0; n < limit; ++n) { + + boost::shared_ptr p = _monitor_out->output()->ports().port(DataType::AUDIO, n); + string connect_to; + if (outputs[DataType::AUDIO].size() > (n % mod)) { + connect_to = outputs[DataType::AUDIO][n % mod]; + } + + if (!connect_to.empty()) { + if (_monitor_out->output()->connect (p, connect_to, this)) { + error << string_compose ( + _("cannot connect control output %1 to %2"), + n, connect_to) + << endmsg; + break; } } } @@ -638,14 +798,32 @@ Session::when_engine_running () } } - _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty)); + /* Hold process lock while doing this so that we don't hear bits and + * pieces of audio as we work on each route. + */ + + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - /* hook us up to the engine */ + /* Connect tracks to monitor section. Note that in an + existing session, the internal sends will already exist, but we want the + routes to notice that they connect to the control out specifically. + */ - BootMessage (_("Connect to engine")); - _engine.set_session (this); - update_latency_compensation (true); + boost::shared_ptr rls = routes.reader (); + + PBD::Unwinder uw (ignore_route_processor_changes, true); + + for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) { + + if ((*x)->is_monitor()) { + /* relax */ + } else if ((*x)->is_master()) { + /* relax */ + } else { + (*x)->enable_monitor_send (); + } + } } void @@ -686,35 +864,7 @@ Session::hookup_io () /* Tell all IO objects to connect themselves together */ IO::enable_connecting (); - MIDI::Port::MakeConnections (); - - /* Now reset all panners */ - - Delivery::reset_panners (); - - /* Connect tracks to monitor/listen bus if there is one. Note that in an - existing session, the internal sends will already exist, but we want the - routes to notice that they connect to the control out specifically. - */ - - if (_monitor_out) { - boost::shared_ptr r = routes.reader (); - for (RouteList::iterator x = r->begin(); x != r->end(); ++x) { - - if ((*x)->is_monitor()) { - - /* relax */ - - } else if ((*x)->is_master()) { - - /* relax */ - - } else { - - (*x)->listen_via_monitor (); - } - } - } + MIDI::JackMIDIPort::MakeConnections (); /* Anyone who cares about input state, wake up and do something */ @@ -776,21 +926,11 @@ 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->monitor_input (yn); + tr->request_jack_monitors_input (yn); } } } -void -Session::reset_input_monitor_state () -{ - if (transport_rolling()) { - set_track_monitor_input_status (Config->get_monitoring_model() == HardwareMonitoring && !config.get_auto_input()); - } else { - set_track_monitor_input_status (Config->get_monitoring_model() == HardwareMonitoring); - } -} - void Session::auto_punch_start_changed (Location* location) { @@ -820,10 +960,25 @@ Session::auto_punch_changed (Location* location) replace_event (SessionEvent::PunchOut, when_to_stop); } +/** @param loc A loop location. + * @param pos Filled in with the start time of the required fade-out (in session frames). + * @param length Filled in with the length of the required fade-out. + */ +void +Session::auto_loop_declick_range (Location* loc, framepos_t & pos, framepos_t & length) +{ + pos = max (loc->start(), loc->end() - 64); + length = loc->end() - pos; +} + void Session::auto_loop_changed (Location* location) { replace_event (SessionEvent::AutoLoop, location->end(), location->start()); + framepos_t dcp; + framecnt_t dcl; + auto_loop_declick_range (location, dcp, dcl); + replace_event (SessionEvent::AutoLoopDeclick, dcp, dcl); if (transport_rolling() && play_loop) { @@ -901,6 +1056,10 @@ Session::set_auto_loop_location (Location* location) loop_connections.drop_connections (); existing->set_auto_loop (false, this); remove_event (existing->end(), SessionEvent::AutoLoop); + framepos_t dcp; + framecnt_t dcl; + auto_loop_declick_range (existing, dcp, dcl); + remove_event (dcp, SessionEvent::AutoLoopDeclick); auto_loop_location_changed (0); } @@ -985,13 +1144,18 @@ Session::handle_locations_changed (Locations::LocationList& locations) void Session::enable_record () { + if (_transport_speed != 0.0 && _transport_speed != 1.0) { + /* no recording at anything except normal speed */ + return; + } + while (1) { RecordState rs = (RecordState) g_atomic_int_get (&_record_status); if (rs == Recording) { break; } - + if (g_atomic_int_compare_and_exchange (&_record_status, rs, Recording)) { _last_record_location = _transport_frame; @@ -1043,6 +1207,8 @@ Session::step_back_from_record () if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) { set_track_monitor_input_status (false); } + + RecordStateChanged (); /* emit signal */ } } @@ -1123,7 +1289,7 @@ Session::audible_frame () const of audible frames, we have not moved yet. `Start position' in this context means the time we last - either started or changed transport direction. + either started, located, or changed transport direction. */ if (_transport_speed > 0.0f) { @@ -1188,7 +1354,7 @@ Session::set_block_size (pframes_t nframes) ::process(). It is therefore fine to do things that block here. */ - + { current_block_size = nframes; @@ -1212,56 +1378,6 @@ Session::set_block_size (pframes_t nframes) } } -struct RouteSorter { - /** @return true to run r1 before r2, otherwise false */ - bool sort_by_rec_enabled (const boost::shared_ptr& r1, const boost::shared_ptr& r2) { - if (r1->record_enabled()) { - if (r2->record_enabled()) { - /* both rec-enabled, just use signal order */ - return r1->order_key(N_("signal")) < r2->order_key(N_("signal")); - } else { - /* r1 rec-enabled, r2 not rec-enabled, run r2 early */ - return false; - } - } else { - if (r2->record_enabled()) { - /* r2 rec-enabled, r1 not rec-enabled, run r1 early */ - return true; - } else { - /* neither rec-enabled, use signal order */ - return r1->order_key(N_("signal")) < r2->order_key(N_("signal")); - } - } - } - - bool operator() (boost::shared_ptr r1, boost::shared_ptr r2) { - if (r2->feeds (r1)) { - /* r1 fed by r2; run r2 early */ - return false; - } else if (r1->feeds (r2)) { - /* r2 fed by r1; run r1 early */ - return true; - } else { - if (r1->not_fed ()) { - if (r2->not_fed ()) { - /* no ardour-based connections inbound to either route. */ - return sort_by_rec_enabled (r1, r2); - } else { - /* r2 has connections, r1 does not; run r1 early */ - return true; - } - } else { - if (r2->not_fed()) { - /* r1 has connections, r2 does not; run r2 early */ - return false; - } else { - /* both r1 and r2 have connections, but not to each other. just use signal order */ - return r1->order_key(N_("signal")) < r2->order_key(N_("signal")); - } - } - } - } -}; static void trace_terminal (boost::shared_ptr r1, boost::shared_ptr rbase) @@ -1317,10 +1433,11 @@ void Session::resort_routes () { /* don't do anything here with signals emitted - by Routes while we are being destroyed. + by Routes during initial setup or while we + are being destroyed. */ - if (_state_of_the_state & Deletion) { + if (_state_of_the_state & (InitialConnecting | Deletion)) { return; } @@ -1331,8 +1448,6 @@ Session::resort_routes () /* writer goes out of scope and forces update */ } - //route_graph->dump(1); - #ifndef NDEBUG boost::shared_ptr rl = routes.reader (); for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { @@ -1350,52 +1465,101 @@ Session::resort_routes () #endif } + +/** This is called whenever we need to rebuild the graph of how we will process + * routes. + * @param r List of routes, in any order. + */ + void Session::resort_routes_using (boost::shared_ptr r) { - RouteList::iterator i, j; - - for (i = r->begin(); i != r->end(); ++i) { + /* We are going to build a directed graph of our routes; + this is where the edges of that graph are put. + */ + + GraphEdges edges; + + /* Go through all routes doing two things: + * + * 1. Collect the edges of the route graph. Each of these edges + * is a pair of routes, one of which directly feeds the other + * either by a JACK connection or by an internal send. + * + * 2. Begin the process of making routes aware of which other + * routes directly or indirectly feed them. This information + * is used by the solo code. + */ + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + /* Clear out the route's list of direct or indirect feeds */ (*i)->clear_fed_by (); - for (j = r->begin(); j != r->end(); ++j) { - - /* although routes can feed themselves, it will - cause an endless recursive descent if we - detect it. so don't bother checking for - self-feeding. - */ - - if (*j == *i) { - continue; - } + for (RouteList::iterator j = r->begin(); j != r->end(); ++j) { bool via_sends_only; - if ((*j)->direct_feeds (*i, &via_sends_only)) { + /* See if this *j feeds *i according to the current state of the JACK + connections and internal sends. + */ + if ((*j)->direct_feeds_according_to_reality (*i, &via_sends_only)) { + /* add the edge to the graph (part #1) */ + edges.add (*j, *i, via_sends_only); + /* tell the route (for part #2) */ (*i)->add_fed_by (*j, via_sends_only); } } } - for (i = r->begin(); i != r->end(); ++i) { - trace_terminal (*i, *i); - } + /* Attempt a topological sort of the route graph */ + boost::shared_ptr sorted_routes = topological_sort (r, edges); + + if (sorted_routes) { + /* We got a satisfactory topological sort, so there is no feedback; + use this new graph. + + Note: the process graph rechain does not require a + topologically-sorted list, but hey ho. + */ + if (_process_graph) { + _process_graph->rechain (sorted_routes, edges); + } + + _current_route_graph = edges; - RouteSorter cmp; - r->sort (cmp); + /* Complete the building of the routes' lists of what directly + or indirectly feeds them. + */ + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + trace_terminal (*i, *i); + } - route_graph->rechain (r); + *r = *sorted_routes; #ifndef NDEBUG - 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 ("signal"))); - } + 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))); + } #endif + SuccessfulGraphSort (); /* EMIT SIGNAL */ + + } else { + /* The topological sort failed, so we have a problem. Tell everyone + and stick to the old graph; this will continue to be processed, so + until the feedback is fixed, what is played back will not quite + reflect what is actually connected. Note also that we do not + do trace_terminal here, as it would fail due to an endless recursion, + so the solo code will think that everything is still connected + as it was before. + */ + + FeedbackDetected (); /* EMIT SIGNAL */ + } + } /** Find a route name starting with \a base, maybe followed by the @@ -1452,24 +1616,25 @@ Session::count_existing_track_channels (ChanCount& in, ChanCount& out) } /** Caller must not hold process lock - * @param name_template string to use for the start of the name, or "" to use "Midi". + * @param name_template string to use for the start of the name, or "" to use "MIDI". + * @param instrument plugin info for the instrument to insert pre-fader, if any */ list > -Session::new_midi_track (TrackMode mode, RouteGroup* route_group, uint32_t how_many, string name_template) +Session::new_midi_track (const ChanCount& input, const ChanCount& output, boost::shared_ptr instrument, + TrackMode mode, RouteGroup* route_group, uint32_t how_many, string name_template) { char track_name[32]; uint32_t track_id = 0; string port; RouteList new_routes; list > ret; - uint32_t control_id; - control_id = ntracks() + nbusses(); + cerr << "Adding MIDI track with in = " << input << " out = " << output << endl; - bool const use_number = (how_many != 1); + bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("MIDI"); while (how_many) { - if (!find_route_name (name_template.empty() ? _("Midi") : name_template, ++track_id, track_name, sizeof(track_name), use_number)) { + if (!find_route_name (name_template.empty() ? _("MIDI") : name_template, ++track_id, track_name, sizeof(track_name), use_number)) { error << "cannot find name for new midi track" << endmsg; goto failed; } @@ -1486,17 +1651,17 @@ Session::new_midi_track (TrackMode mode, RouteGroup* route_group, uint32_t how_m track->use_new_diskstream(); #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - boost_debug_shared_ptr_mark_interesting (track.get(), "Track"); + // boost_debug_shared_ptr_mark_interesting (track.get(), "Track"); #endif { - Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - if (track->input()->ensure_io (ChanCount(DataType::MIDI, 1), false, this)) { - error << "cannot configure 1 in/1 out configuration for new midi track" << endmsg; + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + if (track->input()->ensure_io (input, false, this)) { + error << "cannot configure " << input << " out configuration for new midi track" << endmsg; goto failed; } - if (track->output()->ensure_io (ChanCount(DataType::MIDI, 1), false, this)) { - error << "cannot configure 1 in/1 out configuration for new midi track" << endmsg; + if (track->output()->ensure_io (output, false, this)) { + error << "cannot configure " << output << " out configuration for new midi track" << endmsg; goto failed; } } @@ -1508,7 +1673,10 @@ Session::new_midi_track (TrackMode mode, RouteGroup* route_group, uint32_t how_m } track->DiskstreamChanged.connect_same_thread (*this, boost::bind (&Session::resort_routes, this)); - track->set_remote_control_id (control_id); + + if (Config->get_remote_model() == UserOrdered) { + track->set_remote_control_id (next_control_id()); + } new_routes.push_back (track); ret.push_back (track); @@ -1530,7 +1698,16 @@ Session::new_midi_track (TrackMode mode, RouteGroup* route_group, uint32_t how_m failed: if (!new_routes.empty()) { - add_routes (new_routes, true, true); + add_routes (new_routes, true, true, true); + + if (instrument) { + for (RouteList::iterator r = new_routes.begin(); r != new_routes.end(); ++r) { + PluginPtr plugin = instrument->load (*this); + boost::shared_ptr p (new PluginInsert (*this, plugin)); + (*r)->add_processor (p, PreFader); + + } + } } return ret; @@ -1577,7 +1754,7 @@ Session::auto_connect_route (boost::shared_ptr route, ChanCount& existing return; } - Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock (), Glib::NOT_LOCK); + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock (), Glib::Threads::NOT_LOCK); if (with_lock) { lm.acquire (); @@ -1680,20 +1857,16 @@ Session::auto_connect_route (boost::shared_ptr route, ChanCount& existing * @param name_template string to use for the start of the name, or "" to use "Audio". */ list< boost::shared_ptr > -Session::new_audio_track ( - int input_channels, int output_channels, TrackMode mode, RouteGroup* route_group, uint32_t how_many, string name_template - ) +Session::new_audio_track (int input_channels, int output_channels, TrackMode mode, RouteGroup* route_group, + uint32_t how_many, string name_template) { char track_name[32]; uint32_t track_id = 0; string port; RouteList new_routes; list > ret; - uint32_t control_id; - - control_id = ntracks() + nbusses() + 1; - bool const use_number = (how_many != 1); + bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Audio"); while (how_many) { if (!find_route_name (name_template.empty() ? _("Audio") : name_template, ++track_id, track_name, sizeof(track_name), use_number)) { @@ -1713,10 +1886,10 @@ Session::new_audio_track ( track->use_new_diskstream(); #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - boost_debug_shared_ptr_mark_interesting (track.get(), "Track"); + // boost_debug_shared_ptr_mark_interesting (track.get(), "Track"); #endif { - Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); if (track->input()->ensure_io (ChanCount(DataType::AUDIO, input_channels), false, this)) { error << string_compose ( @@ -1742,8 +1915,9 @@ Session::new_audio_track ( track->non_realtime_input_change(); track->DiskstreamChanged.connect_same_thread (*this, boost::bind (&Session::resort_routes, this)); - track->set_remote_control_id (control_id); - ++control_id; + if (Config->get_remote_model() == UserOrdered) { + track->set_remote_control_id (next_control_id()); + } new_routes.push_back (track); ret.push_back (track); @@ -1765,39 +1939,12 @@ Session::new_audio_track ( failed: if (!new_routes.empty()) { - add_routes (new_routes, true, true); + add_routes (new_routes, true, true, true); } return ret; } -void -Session::set_remote_control_ids () -{ - RemoteModel m = Config->get_remote_model(); - bool emit_signal = false; - - boost::shared_ptr r = routes.reader (); - - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - if (MixerOrdered == m) { - int32_t order = (*i)->order_key(N_("signal")); - (*i)->set_remote_control_id (order+1, false); - emit_signal = true; - } else if (EditorOrdered == m) { - int32_t order = (*i)->order_key(N_("editor")); - (*i)->set_remote_control_id (order+1, false); - emit_signal = true; - } else if (UserOrdered == m) { - //do nothing ... only changes to remote id's are initiated by user - } - } - - if (emit_signal) { - Route::RemoteControlIDChange(); - } -} - /** Caller must not hold process lock. * @param name_template string to use for the start of the name, or "" to use "Bus". */ @@ -1808,11 +1955,9 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r uint32_t bus_id = 0; string port; RouteList ret; - uint32_t control_id; - - control_id = ntracks() + nbusses() + 1; - bool const use_number = (how_many != 1); + bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Bus"); + while (how_many) { if (!find_route_name (name_template.empty () ? _("Bus") : name_template, ++bus_id, bus_name, sizeof(bus_name), use_number)) { error << "cannot find name for new audio bus" << endmsg; @@ -1827,10 +1972,10 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r } #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - boost_debug_shared_ptr_mark_interesting (bus.get(), "Route"); + // boost_debug_shared_ptr_mark_interesting (bus.get(), "Route"); #endif { - Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); if (bus->input()->ensure_io (ChanCount(DataType::AUDIO, input_channels), false, this)) { error << string_compose (_("cannot configure %1 in/%2 out configuration for new audio track"), @@ -1851,8 +1996,9 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r if (route_group) { route_group->add (bus); } - bus->set_remote_control_id (control_id); - ++control_id; + if (Config->get_remote_model() == UserOrdered) { + bus->set_remote_control_id (next_control_id()); + } bus->add_internal_return (); @@ -1876,7 +2022,7 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r failure: if (!ret.empty()) { - add_routes (ret, true, true); + add_routes (ret, false, true, true); // autoconnect outputs only } return ret; @@ -1886,7 +2032,6 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r RouteList Session::new_route_from_template (uint32_t how_many, const std::string& template_path) { - char name[32]; RouteList ret; uint32_t control_id; XMLTree tree; @@ -1898,31 +2043,41 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template XMLNode* node = tree.root(); - control_id = ntracks() + nbusses() + 1; + IO::disable_connecting (); + + control_id = next_control_id (); while (how_many) { - XMLNode node_copy (*node); // make a copy so we can change the name if we need to + XMLNode node_copy (*node); - std::string node_name = IO::name_from_state (*node_copy.children().front()); + /* Remove IDs of everything so that new ones are used */ + node_copy.remove_property_recursively (X_("id")); - /* generate a new name by adding a number to the end of the template name */ - if (!find_route_name (node_name.c_str(), ++number, name, sizeof(name), true)) { - fatal << _("Session: UINT_MAX routes? impossible!") << endmsg; - /*NOTREACHED*/ - } - - /* set IO children to use the new name */ - XMLNodeList const & children = node_copy.children (); - for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) { - if ((*i)->name() == IO::state_node_name) { - IO::set_name_in_state (**i, name); + try { + string const route_name = node_copy.property(X_("name"))->value (); + + /* generate a new name by adding a number to the end of the template name */ + char name[32]; + if (!find_route_name (route_name.c_str(), ++number, name, sizeof(name), true)) { + fatal << _("Session: UINT_MAX routes? impossible!") << endmsg; + /*NOTREACHED*/ } - } - Track::zero_diskstream_id_in_xml (node_copy); + /* set this name in the XML description that we are about to use */ + Route::set_name_in_state (node_copy, name); - try { + /* trim bitslots from listen sends so that new ones are used */ + XMLNodeList children = node_copy.children (); + for (XMLNodeList::iterator i = children.begin(); i != children.end(); ++i) { + if ((*i)->name() == X_("Processor")) { + XMLProperty* role = (*i)->property (X_("role")); + if (role && role->value() == X_("Listen")) { + (*i)->remove_property (X_("bitslot")); + } + } + } + boost::shared_ptr route (XMLRouteFactory (node_copy, 3000)); if (route == 0) { @@ -1936,7 +2091,7 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template loading this normally happens in a different way. */ - Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); IOChange change (IOChange::Type (IOChange::ConfigurationChanged | IOChange::ConnectionsChanged)); change.after = route->input()->n_ports(); @@ -1966,17 +2121,19 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template out: if (!ret.empty()) { - add_routes (ret, true, true); + add_routes (ret, true, true, true); + IO::enable_connecting (); } return ret; } void -Session::add_routes (RouteList& new_routes, bool auto_connect, bool save) +Session::add_routes (RouteList& new_routes, bool input_auto_connect, bool output_auto_connect, bool save) { ChanCount existing_inputs; ChanCount existing_outputs; + uint32_t order = next_control_id(); count_existing_track_channels (existing_inputs, existing_outputs); @@ -1985,7 +2142,6 @@ Session::add_routes (RouteList& new_routes, bool auto_connect, bool save) boost::shared_ptr r = writer.get_copy (); r->insert (r->end(), new_routes.begin(), new_routes.end()); - /* if there is no control out and we're not in the middle of loading, resort the graph here. if there is a control out, we will resort toward the end of this method. if we are in the middle of loading, @@ -2008,7 +2164,6 @@ Session::add_routes (RouteList& new_routes, bool auto_connect, bool save) r->mute_changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this, _1)); r->output()->changed.connect_same_thread (*this, boost::bind (&Session::set_worst_io_latencies_x, this, _1, _2)); r->processors_changed.connect_same_thread (*this, boost::bind (&Session::route_processors_changed, this, _1)); - r->order_key_changed.connect_same_thread (*this, boost::bind (&Session::route_order_key_changed, this)); if (r->is_master()) { _master_out = r; @@ -2031,20 +2186,42 @@ Session::add_routes (RouteList& new_routes, bool auto_connect, bool save) } } - if (auto_connect) { - auto_connect_route (r, existing_inputs, existing_outputs, true); + if (input_auto_connect || output_auto_connect) { + auto_connect_route (r, existing_inputs, existing_outputs, true, input_auto_connect); + } + + /* order keys are a GUI responsibility but we need to set up + reasonable defaults because they also affect the remote control + ID in most situations. + */ + + if (!r->has_order_key (EditorSort)) { + if (r->is_hidden()) { + /* use an arbitrarily high value */ + r->set_order_key (EditorSort, UINT_MAX); + r->set_order_key (MixerSort, 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); + order++; + } } } if (_monitor_out && IO::connecting_legal) { - for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x) { - if ((*x)->is_monitor()) { - /* relax */ - } else if ((*x)->is_master()) { - /* relax */ - } else { - (*x)->listen_via_monitor (); + { + Glib::Threads::Mutex::Lock lm (_engine.process_lock()); + + for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x) { + if ((*x)->is_monitor()) { + /* relax */ + } else if ((*x)->is_master()) { + /* relax */ + } else { + (*x)->enable_monitor_send (); + } } } @@ -2058,7 +2235,6 @@ Session::add_routes (RouteList& new_routes, bool auto_connect, bool save) } RouteAdded (new_routes); /* EMIT SIGNAL */ - Route::RemoteControlIDChange (); /* EMIT SIGNAL */ } void @@ -2108,7 +2284,8 @@ Session::globally_add_internal_sends (boost::shared_ptr dest, Placement p boost::shared_ptr t (new RouteList); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - if (include_buses || boost::dynamic_pointer_cast(*i)) { + /* no MIDI sends because there are no MIDI busses yet */ + if (include_buses || boost::dynamic_pointer_cast(*i)) { t->push_back (*i); } } @@ -2119,22 +2296,29 @@ Session::globally_add_internal_sends (boost::shared_ptr dest, Placement p void Session::add_internal_sends (boost::shared_ptr dest, Placement p, boost::shared_ptr senders) { - if (dest->is_monitor() || dest->is_master()) { + for (RouteList::iterator i = senders->begin(); i != senders->end(); ++i) { + add_internal_send (dest, (*i)->before_processor_for_placement (p), *i); + } +} + +void +Session::add_internal_send (boost::shared_ptr dest, int index, boost::shared_ptr sender) +{ + add_internal_send (dest, sender->before_processor_for_index (index), sender); +} + +void +Session::add_internal_send (boost::shared_ptr dest, boost::shared_ptr before, boost::shared_ptr sender) +{ + if (sender->is_monitor() || sender->is_master() || sender == dest || dest->is_monitor() || dest->is_master()) { return; } if (!dest->internal_return()) { - dest->add_internal_return(); + dest->add_internal_return (); } - for (RouteList::iterator i = senders->begin(); i != senders->end(); ++i) { - - if ((*i)->is_monitor() || (*i)->is_master() || (*i) == dest) { - continue; - } - - (*i)->listen_via (dest, p); - } + sender->add_aux_send (dest, before); graph_reordered (); } @@ -2142,7 +2326,7 @@ Session::add_internal_sends (boost::shared_ptr dest, Placement p, boost:: void Session::remove_route (boost::shared_ptr route) { - if (((route == _master_out) || (route == _monitor_out)) && !Config->get_allow_special_bus_removal()) { + if (route == _master_out) { return; } @@ -2164,13 +2348,6 @@ Session::remove_route (boost::shared_ptr route) } if (route == _monitor_out) { - - /* cancel control outs for all routes */ - - for (RouteList::iterator r = rs->begin(); r != rs->end(); ++r) { - (*r)->drop_listen (_monitor_out); - } - _monitor_out.reset (); } @@ -2211,7 +2388,9 @@ Session::remove_route (boost::shared_ptr route) */ resort_routes (); - route_graph->clear_other_chain (); + if (_process_graph) { + _process_graph->clear_other_chain (); + } /* get rid of it from the dead wood collection in the route list manager */ @@ -2223,8 +2402,6 @@ Session::remove_route (boost::shared_ptr route) route->drop_references (); - sync_order_keys (N_("session")); - Route::RemoteControlIDChange(); /* EMIT SIGNAL */ /* save the new state of the world */ @@ -2304,6 +2481,8 @@ Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr wpr) void Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_ptr wpr) { + DEBUG_TRACE (DEBUG::Solo, string_compose ("route solo change, self = %1\n", self_solo_change)); + if (!self_solo_change) { // session doesn't care about changes to soloed-by-others return; @@ -2311,16 +2490,12 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p if (solo_update_disabled) { // We know already + DEBUG_TRACE (DEBUG::Solo, "solo update disabled - changed ignored\n"); return; } boost::shared_ptr route = wpr.lock (); - - if (!route) { - /* should not happen */ - error << string_compose (_("programming error: %1"), X_("invalid route weak ptr passed to route_solo_changed")) << endmsg; - return; - } + assert (route); boost::shared_ptr r = routes.reader (); int32_t delta; @@ -2331,42 +2506,77 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p delta = -1; } + RouteGroup* rg = route->route_group (); + bool leave_group_alone = (rg && rg->is_active() && rg->is_solo()); + if (delta == 1 && Config->get_exclusive_solo()) { - /* new solo: disable all other solos */ + + /* new solo: disable all other solos, but not the group if its solo-enabled */ + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - if ((*i) == route || (*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_hidden()) { + if ((*i) == route || (*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_hidden() || + (leave_group_alone && ((*i)->route_group() == rg))) { continue; } (*i)->set_solo (false, this); } } + DEBUG_TRACE (DEBUG::Solo, string_compose ("propagate solo change, delta = %1\n", delta)); + solo_update_disabled = true; RouteList uninvolved; + DEBUG_TRACE (DEBUG::Solo, string_compose ("%1\n", route->name())); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { bool via_sends_only; bool in_signal_flow; - if ((*i) == route || (*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_hidden()) { + if ((*i) == route || (*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_hidden() || + (leave_group_alone && ((*i)->route_group() == rg))) { continue; } in_signal_flow = false; + DEBUG_TRACE (DEBUG::Solo, string_compose ("check feed from %1\n", (*i)->name())); + if ((*i)->feeds (route, &via_sends_only)) { if (!via_sends_only) { if (!route->soloed_by_others_upstream()) { (*i)->mod_solo_by_others_downstream (delta); } - in_signal_flow = true; } + in_signal_flow = true; + } else { + DEBUG_TRACE (DEBUG::Solo, "\tno feed from\n"); } + + DEBUG_TRACE (DEBUG::Solo, string_compose ("check feed to %1\n", (*i)->name())); if (route->feeds (*i, &via_sends_only)) { - (*i)->mod_solo_by_others_upstream (delta); + /* propagate solo upstream only if routing other than + sends is involved, but do consider the other route + (*i) to be part of the signal flow even if only + sends are involved. + */ + DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 feeds %2 via sends only %3 sboD %4 sboU %5\n", + route->name(), + (*i)->name(), + via_sends_only, + route->soloed_by_others_downstream(), + route->soloed_by_others_upstream())); + if (!via_sends_only) { + if (!route->soloed_by_others_downstream()) { + DEBUG_TRACE (DEBUG::Solo, string_compose ("\tmod %1 by %2\n", (*i)->name(), delta)); + (*i)->mod_solo_by_others_upstream (delta); + } + } in_signal_flow = true; + } else { + DEBUG_TRACE (DEBUG::Solo, "\tno feed to\n"); } if (!in_signal_flow) { @@ -2375,6 +2585,8 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p } solo_update_disabled = false; + DEBUG_TRACE (DEBUG::Solo, "propagation complete\n"); + update_route_solo_state (r); /* now notify that the mute state of the routes not involved in the signal @@ -2382,6 +2594,7 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p */ for (RouteList::iterator i = uninvolved.begin(); i != uninvolved.end(); ++i) { + DEBUG_TRACE (DEBUG::Solo, string_compose ("mute change for %1\n", (*i)->name())); (*i)->mute_changed (this); } @@ -2465,6 +2678,62 @@ Session::io_name_is_legal (const std::string& name) return true; } +void +Session::set_exclusive_input_active (boost::shared_ptr rt, bool /*others_on*/) +{ + RouteList rl; + vector connections; + + PortSet& ps (rt->input()->ports()); + + for (PortSet::iterator p = ps.begin(); p != ps.end(); ++p) { + p->get_connections (connections); + } + + for (vector::iterator s = connections.begin(); s != connections.end(); ++s) { + routes_using_input_from (*s, rl); + } + + /* scan all relevant routes to see if others are on or off */ + + bool others_are_already_on = false; + + for (RouteList::iterator r = rl.begin(); r != rl.end(); ++r) { + if ((*r) != rt) { + boost::shared_ptr mt = boost::dynamic_pointer_cast (*r); + if (mt) { + if (mt->input_active()) { + others_are_already_on = true; + break; + } + } + } + } + + /* globally reverse other routes */ + + for (RouteList::iterator r = rl.begin(); r != rl.end(); ++r) { + if ((*r) != rt) { + boost::shared_ptr mt = boost::dynamic_pointer_cast (*r); + if (mt) { + mt->set_input_active (!others_are_already_on); + } + } + } +} + +void +Session::routes_using_input_from (const string& str, RouteList& rl) +{ + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + if ((*i)->input()->connected_to (str)) { + rl.push_back (*i); + } + } +} + boost::shared_ptr Session::route_by_name (string name) { @@ -2493,6 +2762,21 @@ Session::route_by_id (PBD::ID id) return boost::shared_ptr ((Route*) 0); } +boost::shared_ptr +Session::track_by_diskstream_id (PBD::ID id) +{ + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + boost::shared_ptr t = boost::dynamic_pointer_cast (*i); + if (t && t->using_diskstream_id (id)) { + return t; + } + } + + return boost::shared_ptr (); +} + boost::shared_ptr Session::route_by_remote_id (uint32_t id) { @@ -2599,7 +2883,7 @@ Session::find_whole_file_parent (boost::shared_ptr child) const RegionFactory::RegionMap::const_iterator i; boost::shared_ptr region; - Glib::Mutex::Lock lm (region_lock); + Glib::Threads::Mutex::Lock lm (region_lock); for (i = regions.begin(); i != regions.end(); ++i) { @@ -2645,7 +2929,7 @@ Session::destroy_sources (list > srcs) for (list >::iterator s = srcs.begin(); s != srcs.end(); ) { { - Glib::Mutex::Lock ls (source_lock); + Glib::Threads::Mutex::Lock ls (source_lock); /* remove from the main source list */ sources.erase ((*s)->id()); } @@ -2698,7 +2982,7 @@ Session::add_source (boost::shared_ptr source) entry.second = source; { - Glib::Mutex::Lock lm (source_lock); + Glib::Threads::Mutex::Lock lm (source_lock); result = sources.insert (entry); } @@ -2706,6 +2990,14 @@ Session::add_source (boost::shared_ptr source) /* yay, new source */ + boost::shared_ptr fs = boost::dynamic_pointer_cast (source); + + if (fs) { + if (!fs->within_session()) { + ensure_search_path_includes (Glib::path_get_dirname (fs->path()), fs->type()); + } + } + set_dirty(); boost::shared_ptr afs; @@ -2735,14 +3027,14 @@ Session::remove_source (boost::weak_ptr src) } { - Glib::Mutex::Lock lm (source_lock); + Glib::Threads::Mutex::Lock lm (source_lock); if ((i = sources.find (source->id())) != sources.end()) { sources.erase (i); } } - if (!_state_of_the_state & InCleanup) { + if (!(_state_of_the_state & InCleanup)) { /* save state so we don't end up with a session file referring to non-existent sources. @@ -2755,7 +3047,7 @@ Session::remove_source (boost::weak_ptr src) boost::shared_ptr Session::source_by_id (const PBD::ID& id) { - Glib::Mutex::Lock lm (source_lock); + Glib::Threads::Mutex::Lock lm (source_lock); SourceMap::iterator i; boost::shared_ptr source; @@ -2769,7 +3061,7 @@ Session::source_by_id (const PBD::ID& id) boost::shared_ptr Session::source_by_path_and_channel (const string& path, uint16_t chn) { - Glib::Mutex::Lock lm (source_lock); + Glib::Threads::Mutex::Lock lm (source_lock); for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) { boost::shared_ptr afs @@ -2786,7 +3078,7 @@ uint32_t Session::count_sources_by_origin (const string& path) { uint32_t cnt = 0; - Glib::Mutex::Lock lm (source_lock); + Glib::Threads::Mutex::Lock lm (source_lock); for (SourceMap::iterator i = sources.begin(); i != sources.end(); ++i) { boost::shared_ptr fs @@ -2916,7 +3208,7 @@ Session::new_source_path_from_name (DataType type, const string& name) SessionDirectory sdir(get_best_session_directory_for_new_source()); - sys::path p; + std::string p; if (type == DataType::AUDIO) { p = sdir.sound_path(); } else if (type == DataType::MIDI) { @@ -2926,16 +3218,13 @@ Session::new_source_path_from_name (DataType type, const string& name) return ""; } - p /= name; - return p.to_string(); + return Glib::build_filename (p, name); } string Session::peak_path (string base) const { - sys::path peakfile_path(_session_dir->peak_path()); - peakfile_path /= base + peakfile_suffix; - return peakfile_path.to_string(); + 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 */ @@ -2999,7 +3288,7 @@ Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t cha SessionDirectory sdir((*i).path); - string spath = sdir.sound_path().to_string(); + 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" @@ -3062,12 +3351,11 @@ Session::new_midi_source_name (const string& base) SessionDirectory sdir((*i).path); - sys::path p = sdir.midi_path(); - p /= legalized; + std::string p = Glib::build_filename (sdir.midi_path(), legalized); - snprintf (buf, sizeof(buf), "%s-%u.mid", p.to_string().c_str(), cnt); + snprintf (buf, sizeof(buf), "%s-%u.mid", p.c_str(), cnt); - if (sys::exists (buf)) { + if (Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) { existing++; } } @@ -3171,12 +3459,9 @@ Session::audition_playlist () void Session::non_realtime_set_audition () { - if (!pending_audition_region) { - auditioner->audition_current_playlist (); - } else { - auditioner->audition_region (pending_audition_region); - pending_audition_region.reset (); - } + assert (pending_audition_region); + auditioner->audition_region (pending_audition_region); + pending_audition_region.reset (); AuditionActive (true); /* EMIT SIGNAL */ } @@ -3206,7 +3491,7 @@ Session::RoutePublicOrderSorter::operator() (boost::shared_ptr a, boost:: if (b->is_monitor()) { return false; } - return a->order_key(N_("signal")) < b->order_key(N_("signal")); + return a->order_key (MixerSort) < b->order_key (MixerSort); } bool @@ -3252,9 +3537,18 @@ Session::graph_reordered () } } -framecnt_t +/** @return Number of frames that there is disk space available to write, + * if known. + */ +boost::optional Session::available_capture_duration () { + Glib::Threads::Mutex::Lock lm (space_lock); + + if (_total_free_4k_blocks_uncertain) { + return boost::optional (); + } + float sample_bytes_on_disk = 4.0; // keep gcc happy switch (config.get_native_file_data_format()) { @@ -3415,6 +3709,26 @@ Session::next_send_id () } } +uint32_t +Session::next_aux_send_id () +{ + /* this doesn't really loop forever. just think about it */ + + while (true) { + for (boost::dynamic_bitset::size_type n = 0; n < aux_send_bitset.size(); ++n) { + if (!aux_send_bitset[n]) { + aux_send_bitset[n] = true; + return n; + + } + } + + /* none available, so resize and try again */ + + aux_send_bitset.resize (aux_send_bitset.size() + 16, false); + } +} + uint32_t Session::next_return_id () { @@ -3447,6 +3761,18 @@ Session::mark_send_id (uint32_t id) send_bitset[id] = true; } +void +Session::mark_aux_send_id (uint32_t id) +{ + if (id >= aux_send_bitset.size()) { + aux_send_bitset.resize (id+16, false); + } + if (aux_send_bitset[id]) { + warning << string_compose (_("aux send ID %1 appears to be in use already"), id) << endmsg; + } + aux_send_bitset[id] = true; +} + void Session::mark_return_id (uint32_t id) { @@ -3479,6 +3805,14 @@ Session::unmark_send_id (uint32_t id) } } +void +Session::unmark_aux_send_id (uint32_t id) +{ + if (id < aux_send_bitset.size()) { + aux_send_bitset[id] = false; + } +} + void Session::unmark_return_id (uint32_t id) { @@ -3495,56 +3829,6 @@ Session::unmark_insert_id (uint32_t id) } } - -/* Named Selection management */ - -boost::shared_ptr -Session::named_selection_by_name (string name) -{ - Glib::Mutex::Lock lm (named_selection_lock); - for (NamedSelectionList::iterator i = named_selections.begin(); i != named_selections.end(); ++i) { - if ((*i)->name == name) { - return *i; - } - } - return boost::shared_ptr(); -} - -void -Session::add_named_selection (boost::shared_ptr named_selection) -{ - { - Glib::Mutex::Lock lm (named_selection_lock); - named_selections.insert (named_selections.begin(), named_selection); - } - - set_dirty(); - - NamedSelectionAdded (); /* EMIT SIGNAL */ -} - -void -Session::remove_named_selection (boost::shared_ptr named_selection) -{ - bool removed = false; - - { - Glib::Mutex::Lock lm (named_selection_lock); - - NamedSelectionList::iterator i = find (named_selections.begin(), named_selections.end(), named_selection); - - if (i != named_selections.end()) { - named_selections.erase (i); - set_dirty(); - removed = true; - } - } - - if (removed) { - NamedSelectionRemoved (); /* EMIT SIGNAL */ - } -} - void Session::reset_native_file_format () { @@ -3613,7 +3897,9 @@ Session::freeze_all (InterThreadInfo& itt) boost::shared_ptr Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, bool /*overwrite*/, vector >& srcs, - InterThreadInfo& itt, bool enable_processing) + InterThreadInfo& itt, + boost::shared_ptr endpoint, bool include_endpoint, + bool for_export) { boost::shared_ptr result; boost::shared_ptr playlist; @@ -3626,7 +3912,7 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, framepos_t to_do; BufferSet buffers; SessionDirectory sdir(get_best_session_directory_for_new_source ()); - const string sound_dir = sdir.sound_path().to_string(); + const string sound_dir = sdir.sound_path(); framepos_t len = end - start; bool need_block_size_reset = false; string ext; @@ -3650,12 +3936,6 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, goto out; } - /* external redirects will be a problem */ - - if (track.has_external_redirects()) { - 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) { @@ -3685,13 +3965,14 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, srcs.push_back (fsource); } - /* tell redirects that care that we are about to use a much larger blocksize */ + /* tell redirects that care that we are about to use a much larger + * blocksize. this will flush all plugins too, so that they are ready + * to be used for this process. + */ need_block_size_reset = true; track.set_block_size (chunk_size); - /* XXX need to flush all redirects */ - position = start; to_do = len; @@ -3709,7 +3990,7 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, this_chunk = min (to_do, chunk_size); - if (track.export_stuff (buffers, start, this_chunk, enable_processing)) { + if (track.export_stuff (buffers, start, this_chunk, endpoint, include_endpoint, for_export)) { goto out; } @@ -3796,6 +4077,12 @@ Session::gain_automation_buffer() const return ProcessThread::gain_automation_buffer (); } +gain_t* +Session::send_gain_automation_buffer() const +{ + return ProcessThread::send_gain_automation_buffer (); +} + pan_t** Session::pan_automation_buffer() const { @@ -3856,31 +4143,6 @@ Session::add_automation_list(AutomationList *al) automation_lists[al->id()] = al; } -void -Session::sync_order_keys (std::string const & base) -{ - if (deletion_in_progress()) { - return; - } - - if (!Config->get_sync_all_route_ordering()) { - /* leave order keys as they are */ - return; - } - - boost::shared_ptr r = routes.reader (); - - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - (*i)->sync_order_keys (base); - } - - Route::SyncOrderKeys (base); // EMIT SIGNAL - - /* this might not do anything */ - - set_remote_control_ids (); -} - /** @return true if there is at least one record-enabled track, otherwise false */ bool Session::have_rec_enabled_track () const @@ -3935,23 +4197,25 @@ Session::solo_control_mode_changed () } } -/** Called when anything about any of our route groups changes (membership, state etc.) */ +/** Called when a property of one of our route groups changes */ void -Session::route_group_changed () +Session::route_group_property_changed (RouteGroup* rg) { - RouteGroupChanged (); /* EMIT SIGNAL */ + RouteGroupPropertyChanged (rg); /* EMIT SIGNAL */ } -vector -Session::get_available_sync_options () const +/** Called when a route is added to one of our route groups */ +void +Session::route_added_to_route_group (RouteGroup* rg, boost::weak_ptr r) { - vector ret; - - ret.push_back (JACK); - ret.push_back (MTC); - ret.push_back (MIDIClock); + RouteAddedToRouteGroup (rg, r); +} - return ret; +/** Called when a route is removed from one of our route groups */ +void +Session::route_removed_from_route_group (RouteGroup* rg, boost::weak_ptr r) +{ + RouteRemovedFromRouteGroup (rg, r); } boost::shared_ptr @@ -4018,13 +4282,6 @@ Session::add_session_range_location (framepos_t start, framepos_t end) _locations->add (_session_range_location); } -/** Called when one of our routes' order keys has changed */ -void -Session::route_order_key_changed () -{ - RouteOrderKeyChanged (); /* EMIT SIGNAL */ -} - void Session::step_edit_status_change (bool yn) { @@ -4065,7 +4322,7 @@ Session::start_time_changed (framepos_t old) Location* l = _locations->auto_loop_location (); - if (l->start() == old) { + if (l && l->start() == old) { l->set_start (s->start(), true); } } @@ -4084,7 +4341,7 @@ Session::end_time_changed (framepos_t old) Location* l = _locations->auto_loop_location (); - if (l->end() == old) { + if (l && l->end() == old) { l->set_end (s->end(), true); } } @@ -4092,35 +4349,39 @@ Session::end_time_changed (framepos_t old) string Session::source_search_path (DataType type) const { - string search_path; + vector s; if (session_dirs.size() == 1) { switch (type) { case DataType::AUDIO: - search_path = _session_dir->sound_path().to_string(); + s.push_back ( _session_dir->sound_path()); break; case DataType::MIDI: - search_path = _session_dir->midi_path().to_string(); + s.push_back (_session_dir->midi_path()); break; } } else { for (vector::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) { SessionDirectory sdir (i->path); - if (!search_path.empty()) { - search_path += ':'; - } switch (type) { case DataType::AUDIO: - search_path += sdir.sound_path().to_string(); + s.push_back (sdir.sound_path()); break; case DataType::MIDI: - search_path += sdir.midi_path().to_string(); + s.push_back (sdir.midi_path()); break; } } } - /* now add user-specified locations + 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)) { + s.push_back (sound_path_2X); + } + } + + /* now check the explicit (possibly user-specified) search path */ vector dirs; @@ -4135,9 +4396,27 @@ Session::source_search_path (DataType type) const } for (vector::iterator i = dirs.begin(); i != dirs.end(); ++i) { - search_path += ':'; - search_path += *i; + vector::iterator si; + + for (si = s.begin(); si != s.end(); ++si) { + if ((*si) == *i) { + break; + } + } + + if (si == 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; @@ -4165,7 +4444,13 @@ Session::ensure_search_path_includes (const string& path, DataType type) split (search_path, dirs, ':'); for (vector::iterator i = dirs.begin(); i != dirs.end(); ++i) { - if (*i == path) { + /* 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. + + On Windows, I think we could just do if (*i == path) here. + */ + if (PBD::equivalent_paths (*i, path)) { return; } } @@ -4223,6 +4508,8 @@ Session::update_latency (bool playback) if (playback) { /* reverse the list so that we work backwards from the last route to run to the first */ + RouteList* rl = routes.reader().get(); + r.reset (new RouteList (*rl)); reverse (r->begin(), r->end()); } @@ -4266,14 +4553,15 @@ Session::post_playback_latency () set_worst_playback_latency (); boost::shared_ptr r = routes.reader (); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { if (!(*i)->is_hidden() && ((*i)->active())) { _worst_track_latency = max (_worst_track_latency, (*i)->update_signal_latency ()); } + } - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - (*i)->set_latency_compensation (_worst_track_latency); - } + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->set_latency_compensation (_worst_track_latency); } } @@ -4294,6 +4582,18 @@ Session::post_capture_latency () } } +void +Session::initialize_latencies () +{ + { + Glib::Threads::Mutex::Lock lm (_engine.process_lock()); + update_latency (false); + update_latency (true); + } + + set_worst_io_latencies (); +} + void Session::set_worst_io_latencies () { @@ -4373,15 +4673,136 @@ Session::update_latency_compensation (bool force_whole_graph) DEBUG_TRACE (DEBUG::Latency, string_compose ("worst signal processing latency: %1 (changed ? %2)\n", _worst_track_latency, (some_track_latency_changed ? "yes" : "no"))); - if (force_whole_graph || some_track_latency_changed) { - /* trigger a full recompute of latency numbers for the graph. - everything else that we need to do will be done in the latency - callback. - */ - _engine.update_total_latencies (); - return; // everything else will be done in the latency callback + DEBUG_TRACE(DEBUG::Latency, "---------------------------- DONE update latency compensation\n\n"); + + if (some_track_latency_changed || force_whole_graph) { + _engine.update_latencies (); + } + + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (!tr) { + continue; + } + tr->set_capture_offset (); + } +} + +char +Session::session_name_is_legal (const string& path) +{ + char illegal_chars[] = { '/', '\\', ':', ';', '\0' }; + + for (int i = 0; illegal_chars[i]; ++i) { + if (path.find (illegal_chars[i]) != string::npos) { + return illegal_chars[i]; + } + } + + return 0; +} + +uint32_t +Session::next_control_id () const +{ + int subtract = 0; + + /* the monitor bus remote ID is in a different + * "namespace" than regular routes. its existence doesn't + * affect normal (low) numbered routes. + */ + + if (_monitor_out) { + subtract++; + } + + return nroutes() - subtract; +} + +void +Session::notify_remote_id_change () +{ + if (deletion_in_progress()) { + return; + } + + switch (Config->get_remote_model()) { + case MixerSort: + case EditorSort: + Route::RemoteControlIDChange (); /* EMIT SIGNAL */ + break; + default: + break; + } +} + +void +Session::sync_order_keys (RouteSortOrderKey sort_key_changed) +{ + if (deletion_in_progress()) { + return; } - DEBUG_TRACE(DEBUG::Latency, "---------------------------- DONE update latency compensation\n\n") + /* tell everyone that something has happened to the sort keys + and let them sync up with the change(s) + this will give objects that manage the sort order keys the + 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))); + + Route::SyncOrderKeys (sort_key_changed); /* EMIT SIGNAL */ + + DEBUG_TRACE (DEBUG::OrderKeys, "\tsync done\n"); +} + +bool +Session::operation_in_progress (GQuark op) const +{ + return (find (_current_trans_quarks.begin(), _current_trans_quarks.end(), op) != _current_trans_quarks.end()); +} + +boost::shared_ptr +Session::ltc_input_port () const +{ + return _ltc_input->nth (0); +} + +boost::shared_ptr +Session::ltc_output_port () const +{ + return _ltc_output->nth (0); +} + +void +Session::reconnect_ltc_input () +{ + if (_ltc_input) { + + string src = Config->get_ltc_source_port(); + + _ltc_input->disconnect (this); + + if (src != _("None") && !src.empty()) { + _ltc_input->nth (0)->connect (src); + } + } } +void +Session::reconnect_ltc_output () +{ + if (_ltc_output) { + +#if 0 + string src = Config->get_ltc_sink_port(); + + _ltc_output->disconnect (this); + + if (src != _("None") && !src.empty()) { + _ltc_output->nth (0)->connect (src); + } +#endif + } +}