X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession.cc;h=559ca640620556e2ca8c35f3d4a64a44a2c1b3e1;hb=f6e869d50d63b9ec5e27f9349392d6725832d49a;hp=9b461d8c69c51494c9cefa1a2eaa86355130ed1d;hpb=bf806a87c3e53d60fa881d91973839f410001c3a;p=ardour.git diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 9b461d8c69..559ca64062 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -45,6 +45,8 @@ #include "pbd/file_utils.h" #include "pbd/convert.h" #include "pbd/strsplit.h" +#include "pbd/strsplit.h" +#include "pbd/unwind.h" #include "ardour/amp.h" #include "ardour/analyser.h" @@ -54,60 +56,52 @@ #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/crossfade.h" -#include "ardour/cycle_timer.h" +#include "ardour/control_protocol_manager.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; @@ -122,21 +116,22 @@ 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 prefix */ Session::Session (AudioEngine &eng, - const string& fullpath, - const string& snapshot_name, + const string& fullpath, + const string& snapshot_name, BusProfile* bus_profile, - string mix_template) - : _engine (eng) + string mix_template) + : _engine (eng) , _target_transport_speed (0.0) , _requested_return_frame (-1) , _session_dir (new SessionDirectory(fullpath)) @@ -146,7 +141,6 @@ 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) , _bundles (new BundleList) @@ -156,12 +150,18 @@ Session::Session (AudioEngine &eng, , 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); - + + 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); _all_route_group->set_active (true, this); @@ -207,11 +207,14 @@ Session::Session (AudioEngine &eng, StartTimeChanged.connect_same_thread (*this, boost::bind (&Session::start_time_changed, this, _1)); EndTimeChanged.connect_same_thread (*this, boost::bind (&Session::end_time_changed, this, _1)); - _is_new = false; + _is_new = false; } Session::~Session () { +#ifdef PT_TIMING + ST.dump ("ST.dump"); +#endif destroy (); } @@ -238,10 +241,6 @@ Session::destroy () delete state_tree; - /* remove all stubfiles that might still be lurking */ - - cleanup_stubfiles (); - /* reset dynamic state version back to default */ Stateful::loading_state_version = 0; @@ -249,7 +248,7 @@ Session::destroy () _butler->drop_references (); delete _butler; delete midi_control_ui; - delete _all_route_group; + delete _all_route_group; if (click_data != default_click) { delete [] click_data; @@ -265,7 +264,7 @@ Session::destroy () routes.flush (); _bundles.flush (); - + AudioDiskstream::free_working_buffers(); /* tell everyone who is still standing that we're about to die */ @@ -301,11 +300,9 @@ Session::destroy () } routes.flush (); - boost::shared_ptr r = routes.reader (); - 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->path(), i->second.use_count())); + 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 (); } @@ -313,17 +310,14 @@ Session::destroy () DEBUG_TRACE (DEBUG::Destruction, "delete route groups\n"); for (list::iterator i = _route_groups.begin(); i != _route_groups.end(); ++i) { - + delete *i; } - Crossfade::set_buffer_size (0); - /* not strictly necessary, but doing it here allows the shared_ptr debugging to work */ playlists.reset (); delete _locations; - delete _speakers; DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n"); @@ -332,24 +326,6 @@ Session::destroy () #endif } -void -Session::set_worst_io_latencies () -{ - _worst_output_latency = 0; - _worst_input_latency = 0; - - if (!_engine.connected()) { - return; - } - - boost::shared_ptr r = routes.reader (); - - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - _worst_output_latency = max (_worst_output_latency, (*i)->output()->latency()); - _worst_input_latency = max (_worst_input_latency, (*i)->input()->latency()); - } -} - void Session::when_engine_running () { @@ -370,7 +346,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 (); @@ -386,24 +362,32 @@ Session::when_engine_running () XMLNode* child = 0; _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 (); - + } else { - + error << _("could not setup Click I/O") << endmsg; _clicking = false; } @@ -423,21 +407,19 @@ Session::when_engine_running () } } } - + 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")); - set_worst_io_latencies (); - if (_clicking) { // XXX HOW TO ALERT UI TO THIS ? DO WE NEED TO? } @@ -465,7 +447,7 @@ Session::when_engine_running () char buf[32]; snprintf (buf, sizeof (buf), _("out %" PRIu32), np+1); - boost::shared_ptr c (new Bundle (buf, true)); + boost::shared_ptr c (new Bundle (buf, true)); c->add_channel (_("mono"), DataType::AUDIO); c->set_port (0, outputs[DataType::AUDIO][np]); @@ -494,7 +476,7 @@ Session::when_engine_running () char buf[32]; snprintf (buf, sizeof (buf), _("in %" PRIu32), np+1); - boost::shared_ptr c (new Bundle (buf, false)); + boost::shared_ptr c (new Bundle (buf, false)); c->add_channel (_("mono"), DataType::AUDIO); c->set_port (0, inputs[DataType::AUDIO][np]); @@ -508,7 +490,7 @@ Session::when_engine_running () char buf[32]; snprintf (buf, sizeof(buf), _("in %" PRIu32 "+%" PRIu32), np + 1, np + 2); - boost::shared_ptr c (new Bundle (buf, false)); + boost::shared_ptr c (new Bundle (buf, false)); c->add_channel (_("L"), DataType::AUDIO); c->set_port (0, inputs[DataType::AUDIO][np]); c->add_channel (_("R"), DataType::AUDIO); @@ -523,20 +505,20 @@ Session::when_engine_running () for (uint32_t np = 0; np < inputs[DataType::MIDI].size(); ++np) { string n = inputs[DataType::MIDI][np]; boost::erase_first (n, X_("alsa_pcm:")); - - boost::shared_ptr c (new Bundle (n, false)); + + boost::shared_ptr c (new Bundle (n, false)); c->add_channel ("", DataType::MIDI); c->set_port (0, inputs[DataType::MIDI][np]); add_bundle (c); } - + /* MIDI output bundles */ for (uint32_t np = 0; np < outputs[DataType::MIDI].size(); ++np) { string n = outputs[DataType::MIDI][np]; boost::erase_first (n, X_("alsa_pcm:")); - boost::shared_ptr c (new Bundle (n, true)); + boost::shared_ptr c (new Bundle (n, true)); c->add_channel ("", DataType::MIDI); c->set_port (0, outputs[DataType::MIDI][np]); add_bundle (c); @@ -544,106 +526,237 @@ 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 + * 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::Mutex::Lock lm (AudioEngine::instance()->process_lock()); + auto_connect_master_bus (); + } - Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock()); - - /* don't connect the master bus outputs if there is a monitor bus */ + _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty)); - if (_master_out && Config->get_auto_connect_standard_busses() && !_monitor_out) { + /* update latencies */ - /* if requested auto-connect the outputs to the first N physical ports. - */ + initialize_latencies (); - uint32_t limit = _master_out->n_outputs().n_total(); + /* hook us up to the engine */ - 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]; - } + BootMessage (_("Connect to engine")); + _engine.set_session (this); +} - 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; - } - } +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::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::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, 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 { - - for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) { - uint32_t mod = n_physical_outputs.get (*t); - uint32_t limit = _monitor_out->n_outputs().get(*t); - - for (uint32_t n = 0; n < limit; ++n) { - - Port* p = _monitor_out->output()->ports().port(*t, n); - string connect_to; - if (outputs[*t].size() > (n % mod)) { - connect_to = outputs[*t][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; - } - } + 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]); + } + + + 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; } } } @@ -651,16 +764,32 @@ Session::when_engine_running () } } - /* catch up on send+insert cnts */ + /* Hold process lock while doing this so that we don't hear bits and + * pieces of audio as we work on each route. + */ + + Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - _state_of_the_state = StateOfTheState (_state_of_the_state & ~(CannotSave|Dirty)); + /* 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. + */ - /* hook us up to the engine */ - BootMessage (_("Connect to engine")); + boost::shared_ptr rls = routes.reader (); - _engine.set_session (this); - _engine.update_total_latencies (); + 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 @@ -672,7 +801,6 @@ Session::hookup_io () _state_of_the_state = StateOfTheState (_state_of_the_state | InitialConnecting); - if (!auditioner) { /* we delay creating the auditioner till now because @@ -680,13 +808,12 @@ Session::hookup_io () */ try { - Auditioner* a = new Auditioner (*this); + boost::shared_ptr a (new Auditioner (*this)); if (a->init()) { - delete a; - throw failed_constructor(); + throw failed_constructor (); } a->use_new_diskstream (); - auditioner.reset (a); + auditioner = a; } catch (failed_constructor& err) { @@ -703,38 +830,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_out, - (Config->get_listen_position() == AfterFaderListen ? PostFader : PreFader), - false, false); - } - } - } + MIDI::JackMIDIPort::MakeConnections (); /* Anyone who cares about input state, wake up and do something */ @@ -764,12 +860,13 @@ Session::track_playlist_changed (boost::weak_ptr wp) if (!track) { return; } - + boost::shared_ptr playlist; if ((playlist = track->playlist()) != 0) { playlist->RegionAdded.connect_same_thread (*this, boost::bind (&Session::playlist_region_added, this, _1)); playlist->RangesMoved.connect_same_thread (*this, boost::bind (&Session::playlist_ranges_moved, this, _1)); + playlist->RegionsExtended.connect_same_thread (*this, boost::bind (&Session::playlist_regions_extended, this, _1)); } } @@ -788,28 +885,14 @@ Session::record_enabling_legal () const } void -Session::reset_input_monitor_state () +Session::set_track_monitor_input_status (bool yn) { - if (transport_rolling()) { - - boost::shared_ptr rl = routes.reader (); - for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { - 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 (Config->get_monitoring_model() == HardwareMonitoring && !config.get_auto_input()); - } - } - - } else { - - boost::shared_ptr rl = routes.reader (); - for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); - if (tr && tr->record_enabled ()) { - //cerr << "switching to input = " << !Config->get_auto_input() << __FILE__ << __LINE__ << endl << endl; - tr->monitor_input (Config->get_monitoring_model() == HardwareMonitoring); - } + boost::shared_ptr rl = routes.reader (); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + 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); } } } @@ -1008,29 +1091,27 @@ 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; MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe)); - + if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) { - - boost::shared_ptr rl = routes.reader (); - for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); - if (tr && tr->record_enabled ()) { - tr->monitor_input (true); - } - } + set_track_monitor_input_status (true); } - + RecordStateChanged (); break; } @@ -1054,14 +1135,7 @@ Session::disable_record (bool rt_context, bool force) } if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) { - - boost::shared_ptr rl = routes.reader (); - for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); - if (tr && tr->record_enabled ()) { - tr->monitor_input (false); - } - } + set_track_monitor_input_status (false); } RecordStateChanged (); /* emit signal */ @@ -1078,15 +1152,10 @@ Session::step_back_from_record () if (g_atomic_int_compare_and_exchange (&_record_status, Recording, Enabled)) { if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) { - boost::shared_ptr rl = routes.reader (); - for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); - if (tr && tr->record_enabled ()) { - //cerr << "switching from input" << __FILE__ << __LINE__ << endl << endl; - tr->monitor_input (false); - } - } + set_track_monitor_input_status (false); } + + RecordStateChanged (); /* emit signal */ } } @@ -1104,9 +1173,9 @@ Session::maybe_enable_record () means that we save pending state of which sources the next record will use, which gives us some chance of recovering from a crash during the record. */ - + save_state ("", true); - + if (_transport_speed) { if (!config.get_punch_in()) { enable_record (); @@ -1136,7 +1205,7 @@ Session::audible_frame () const in the absence of any plugin latency compensation */ - offset = _worst_output_latency; + offset = worst_playback_latency (); if (offset > current_block_size) { offset -= current_block_size; @@ -1232,7 +1301,7 @@ Session::set_block_size (pframes_t nframes) ::process(). It is therefore fine to do things that block here. */ - + { current_block_size = nframes; @@ -1250,84 +1319,17 @@ Session::set_block_size (pframes_t nframes) if (tr) { tr->set_block_size (nframes); } - } - - set_worst_io_latencies (); - } -} - -void -Session::set_default_fade (float /*steepness*/, float /*fade_msecs*/) -{ -#if 0 - framecnt_t fade_frames; - - /* Don't allow fade of less 1 frame */ - - if (fade_msecs < (1000.0 * (1.0/_current_frame_rate))) { - - fade_msecs = 0; - fade_frames = 0; - - } else { - - fade_frames = (framecnt_t) floor (fade_msecs * _current_frame_rate * 0.001); - - } - - default_fade_msecs = fade_msecs; - default_fade_steepness = steepness; - - { - // jlc, WTF is this! - Glib::RWLock::ReaderLock lm (route_lock); - AudioRegion::set_default_fade (steepness, fade_frames); - } - - set_dirty(); + } - /* XXX have to do this at some point */ - /* foreach region using default fade, reset, then - refill_all_diskstream_buffers (); - */ -#endif + set_worst_io_latencies (); + } } -struct RouteSorter { - /** @return true to run r1 before r2, otherwise false */ - 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. just use signal order */ - return r1->order_key(N_("signal")) < r2->order_key(N_("signal")); - } 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) { - boost::shared_ptr r2; + boost::shared_ptr r2; if (r1->feeds (rbase) && rbase->feeds (r1)) { info << string_compose(_("feedback loop setup between %1 and %2"), r1->name(), rbase->name()) << endmsg; @@ -1337,7 +1339,7 @@ trace_terminal (boost::shared_ptr r1, boost::shared_ptr rbase) /* make a copy of the existing list of routes that feed r1 */ Route::FedBy existing (r1->fed_by()); - + /* for each route that feeds r1, recurse, marking it as feeding rbase as well. */ @@ -1347,7 +1349,7 @@ trace_terminal (boost::shared_ptr r1, boost::shared_ptr rbase) /* (*i) went away, ignore it */ continue; } - + /* r2 is a route that feeds r1 which somehow feeds base. mark base as being fed by r2 */ @@ -1378,27 +1380,26 @@ 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; } { RCUWriter writer (routes); - boost::shared_ptr r = writer.get_copy (); + boost::shared_ptr r = writer.get_copy (); resort_routes_using (r); /* 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) { DEBUG_TRACE (DEBUG::Graph, string_compose ("%1 fed by ...\n", (*i)->name())); - + const Route::FedBy& fb ((*i)->fed_by()); for (Route::FedBy::const_iterator f = fb.begin(); f != fb.end(); ++f) { @@ -1411,68 +1412,126 @@ 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 ("signal"))); + } #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 the route name starting with \a base with the lowest \a id. +/** Find a route name starting with \a base, maybe followed by the + * lowest \a id. \a id will always be added if \a definitely_add_number + * is true on entry; otherwise it will only be added if required + * to make the name unique. * - * Names are constructed like e.g. "Audio 3" for base="Audio" and id=3. - * The available route name with the lowest ID will be used, and \a id - * will be set to the ID. + * Names are constructed like e.g. "Audio 3" for base="Audio" and id=3. + * The available route name with the lowest ID will be used, and \a id + * will be set to the ID. * - * \return false if a route name could not be found, and \a track_name - * and \a id do not reflect a free route name. + * \return false if a route name could not be found, and \a track_name + * and \a id do not reflect a free route name. */ bool -Session::find_route_name (const char* base, uint32_t& id, char* name, size_t name_len) +Session::find_route_name (string const & base, uint32_t& id, char* name, size_t name_len, bool definitely_add_number) { + if (!definitely_add_number && route_by_name (base) == 0) { + /* juse use the base */ + snprintf (name, name_len, "%s", base.c_str()); + return true; + } + do { - snprintf (name, name_len, "%s %" PRIu32, base, id); + snprintf (name, name_len, "%s %" PRIu32, base.c_str(), id); if (route_by_name (name) == 0) { return true; @@ -1485,61 +1544,62 @@ Session::find_route_name (const char* base, uint32_t& id, char* name, size_t nam return false; } -/** Count the total ins and outs of all non-hidden routes in the session and return them in in and out */ +/** Count the total ins and outs of all non-hidden tracks in the session and return them in in and out */ void -Session::count_existing_route_channels (ChanCount& in, ChanCount& out) +Session::count_existing_track_channels (ChanCount& in, ChanCount& out) { in = ChanCount::ZERO; out = ChanCount::ZERO; - boost::shared_ptr r = routes.reader (); + + boost::shared_ptr r = routes.reader (); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - if (!(*i)->is_hidden()) { - in += (*i)->n_inputs(); - out += (*i)->n_outputs(); + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && !tr->is_hidden()) { + in += tr->n_inputs(); + out += tr->n_outputs(); } } } -/** Caller must not hold process lock */ +/** Caller must not hold process lock + * @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) +Session::new_midi_track (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; - ChanCount existing_inputs; - ChanCount existing_outputs; string port; RouteList new_routes; list > ret; uint32_t control_id; - count_existing_route_channels (existing_inputs, existing_outputs); + control_id = next_control_id (); - control_id = ntracks() + nbusses(); + bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("MIDI"); while (how_many) { - if (!find_route_name ("Midi", ++track_id, track_name, sizeof(track_name))) { + 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; } - boost::shared_ptr track; - + boost::shared_ptr track; + try { - MidiTrack* mt = new MidiTrack (*this, track_name, Route::Flag (0), mode); + track.reset (new MidiTrack (*this, track_name, Route::Flag (0), mode)); - if (mt->init ()) { - delete mt; + if (track->init ()) { goto failed; } - mt->use_new_diskstream(); + track->use_new_diskstream(); #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - boost_debug_shared_ptr_mark_interesting (mt, "Track"); + // boost_debug_shared_ptr_mark_interesting (track.get(), "Track"); #endif - track = boost::shared_ptr(mt); - { Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); if (track->input()->ensure_io (ChanCount(DataType::MIDI, 1), false, this)) { @@ -1553,8 +1613,6 @@ Session::new_midi_track (TrackMode mode, RouteGroup* route_group, uint32_t how_m } } - auto_connect_route (track.get(), existing_inputs, existing_outputs); - track->non_realtime_input_change(); if (route_group) { @@ -1584,23 +1642,68 @@ Session::new_midi_track (TrackMode mode, RouteGroup* route_group, uint32_t how_m failed: if (!new_routes.empty()) { - add_routes (new_routes, false); - save_state (_current_snapshot_name); + 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; } -/** Caller must hold process lock. - * @param connect_inputs true to connect inputs as well as outputs, false to connect just outputs. +void +Session::midi_output_change_handler (IOChange change, void * /*src*/, boost::weak_ptr wmt) +{ + boost::shared_ptr midi_track (wmt.lock()); + + if (!midi_track) { + return; + } + + if ((change.type & IOChange::ConfigurationChanged) && Config->get_output_auto_connect() != ManualConnect) { + + if (change.after.n_audio() <= change.before.n_audio()) { + return; + } + + /* new audio ports: make sure the audio goes somewhere useful, + unless the user has no-auto-connect selected. + + The existing ChanCounts don't matter for this call as they are only + to do with matching input and output indices, and we are only changing + outputs here. + */ + + ChanCount dummy; + + auto_connect_route (midi_track, dummy, dummy, false, false, ChanCount(), change.before); + } +} + +/** @param connect_inputs true to connect inputs as well as outputs, false to connect just outputs. * @param input_start Where to start from when auto-connecting inputs; e.g. if this is 0, auto-connect starting from input 0. * @param output_start As \a input_start, but for outputs. */ void -Session::auto_connect_route ( - Route* route, ChanCount& existing_inputs, ChanCount& existing_outputs, bool connect_inputs, ChanCount input_start, ChanCount output_start - ) +Session::auto_connect_route (boost::shared_ptr route, ChanCount& existing_inputs, ChanCount& existing_outputs, + bool with_lock, bool connect_inputs, ChanCount input_start, ChanCount output_start) { + if (!IO::connecting_legal) { + return; + } + + Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock (), Glib::NOT_LOCK); + + if (with_lock) { + lm.acquire (); + } + /* If both inputs and outputs are auto-connected to physical ports, use the max of input and output offsets to ensure auto-connected port numbers always match up (e.g. the first audio input and the @@ -1609,14 +1712,18 @@ Session::auto_connect_route ( offset possible. */ + DEBUG_TRACE (DEBUG::Graph, + string_compose("Auto-connect: existing in = %1 out = %2\n", + existing_inputs, existing_outputs)); + const bool in_out_physical = - (Config->get_input_auto_connect() & AutoConnectPhysical) + (Config->get_input_auto_connect() & AutoConnectPhysical) && (Config->get_output_auto_connect() & AutoConnectPhysical) && connect_inputs; const ChanCount in_offset = in_out_physical ? ChanCount::max(existing_inputs, existing_outputs) - : existing_inputs; + : existing_inputs; const ChanCount out_offset = in_out_physical ? ChanCount::max(existing_inputs, existing_outputs) @@ -1631,17 +1738,32 @@ Session::auto_connect_route ( if (!physinputs.empty() && connect_inputs) { uint32_t nphysical_in = physinputs.size(); + + DEBUG_TRACE (DEBUG::Graph, + string_compose("There are %1 physical inputs of type %2\n", + nphysical_in, *t)); + for (uint32_t i = input_start.get(*t); i < route->n_inputs().get(*t) && i < nphysical_in; ++i) { string port; if (Config->get_input_auto_connect() & AutoConnectPhysical) { + DEBUG_TRACE (DEBUG::Graph, + string_compose("Get index %1 + %2 % %3 = %4\n", + in_offset.get(*t), i, nphysical_in, + (in_offset.get(*t) + i) % nphysical_in)); port = physinputs[(in_offset.get(*t) + i) % nphysical_in]; } - if (!port.empty() && route->input()->connect ( - route->input()->ports().port(*t, i), port, this)) { + DEBUG_TRACE (DEBUG::Graph, + string_compose("Connect route %1 IN to %2\n", + route->name(), port)); + + if (!port.empty() && route->input()->connect (route->input()->ports().port(*t, i), port, this)) { break; } + + ChanCount one_added (*t, 1); + existing_inputs += one_added; } } @@ -1650,67 +1772,69 @@ Session::auto_connect_route ( for (uint32_t i = output_start.get(*t); i < route->n_outputs().get(*t); ++i) { string port; - if (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 (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, i % _master_out->input()->n_ports().get(*t))->name(); } } - if (!port.empty() && route->output()->connect ( - route->output()->ports().port(*t, i), port, this)) { + DEBUG_TRACE (DEBUG::Graph, + string_compose("Connect route %1 OUT to %2\n", + route->name(), port)); + + if (!port.empty() && route->output()->connect (route->output()->ports().port(*t, i), port, this)) { break; } + + ChanCount one_added (*t, 1); + existing_outputs += one_added; } } } - - existing_inputs += route->n_inputs(); - existing_outputs += route->n_outputs(); } -/** Caller must not hold process lock */ +/** Caller must not hold process lock + * @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) +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; - ChanCount existing_inputs; - ChanCount existing_outputs; string port; RouteList new_routes; list > ret; uint32_t control_id; - count_existing_route_channels (existing_inputs, existing_outputs); + control_id = next_control_id (); - control_id = ntracks() + nbusses() + 1; + bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Audio"); while (how_many) { - if (!find_route_name ("Audio", ++track_id, track_name, sizeof(track_name))) { + if (!find_route_name (name_template.empty() ? _("Audio") : name_template, ++track_id, track_name, sizeof(track_name), use_number)) { error << "cannot find name for new audio track" << endmsg; goto failed; } - boost::shared_ptr track; - + boost::shared_ptr track; + try { - AudioTrack* at = new AudioTrack (*this, track_name, Route::Flag (0), mode); + track.reset (new AudioTrack (*this, track_name, Route::Flag (0), mode)); - if (at->init ()) { - delete at; + if (track->init ()) { goto failed; } - at->use_new_diskstream(); + track->use_new_diskstream(); #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - boost_debug_shared_ptr_mark_interesting (at, "Track"); + // boost_debug_shared_ptr_mark_interesting (track.get(), "Track"); #endif - track = boost::shared_ptr(at); - { Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); @@ -1721,7 +1845,7 @@ Session::new_audio_track (int input_channels, int output_channels, TrackMode mod << endmsg; goto failed; } - + if (track->output()->ensure_io (ChanCount(DataType::AUDIO, output_channels), false, this)) { error << string_compose ( _("cannot configure %1 in/%2 out configuration for new audio track"), @@ -1729,8 +1853,6 @@ Session::new_audio_track (int input_channels, int output_channels, TrackMode mod << endmsg; goto failed; } - - auto_connect_route (track.get(), existing_inputs, existing_outputs); } if (route_group) { @@ -1763,7 +1885,7 @@ Session::new_audio_track (int input_channels, int output_channels, TrackMode mod failed: if (!new_routes.empty()) { - add_routes (new_routes, true); + add_routes (new_routes, true, true, true); } return ret; @@ -1775,7 +1897,7 @@ Session::set_remote_control_ids () RemoteModel m = Config->get_remote_model(); bool emit_signal = false; - boost::shared_ptr r = routes.reader (); + boost::shared_ptr r = routes.reader (); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { if (MixerOrdered == m) { @@ -1796,41 +1918,38 @@ Session::set_remote_control_ids () } } -/** Caller must not hold process lock */ +/** Caller must not hold process lock. + * @param name_template string to use for the start of the name, or "" to use "Bus". + */ RouteList -Session::new_audio_route (int input_channels, int output_channels, RouteGroup* route_group, uint32_t how_many) +Session::new_audio_route (int input_channels, int output_channels, RouteGroup* route_group, uint32_t how_many, string name_template) { char bus_name[32]; uint32_t bus_id = 0; - ChanCount existing_inputs; - ChanCount existing_outputs; string port; RouteList ret; uint32_t control_id; - count_existing_route_channels (existing_inputs, existing_outputs); - - control_id = ntracks() + nbusses() + 1; + control_id = next_control_id (); + bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Bus"); + while (how_many) { - if (!find_route_name ("Bus", ++bus_id, bus_name, sizeof(bus_name))) { + 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; goto failure; } try { - Route* rt = new Route (*this, bus_name, Route::Flag(0), DataType::AUDIO); + boost::shared_ptr bus (new Route (*this, bus_name, Route::Flag(0), DataType::AUDIO)); - if (rt->init ()) { - delete rt; + if (bus->init ()) { goto failure; } #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - boost_debug_shared_ptr_mark_interesting (rt, "Route"); + // boost_debug_shared_ptr_mark_interesting (bus.get(), "Route"); #endif - boost::shared_ptr bus (rt); - { Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); @@ -1840,16 +1959,14 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r << endmsg; goto failure; } - - + + if (bus->output()->ensure_io (ChanCount(DataType::AUDIO, output_channels), false, this)) { error << string_compose (_("cannot configure %1 in/%2 out configuration for new audio track"), input_channels, output_channels) << endmsg; goto failure; } - - auto_connect_route (bus.get(), existing_inputs, existing_outputs, false); } if (route_group) { @@ -1880,7 +1997,7 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r failure: if (!ret.empty()) { - add_routes (ret, true); + add_routes (ret, false, true, true); // autoconnect outputs only } return ret; @@ -1890,7 +2007,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; @@ -1902,32 +2018,42 @@ 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 (); - while (how_many) { + control_id = next_control_id (); - XMLNode node_copy (*node); // make a copy so we can change the name if we need to + while (how_many) { - std::string node_name = IO::name_from_state (*node_copy.children().front()); + XMLNode node_copy (*node); - /* 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))) { - fatal << _("Session: UINT_MAX routes? impossible!") << endmsg; - /*NOTREACHED*/ - } + /* Remove IDs of everything so that new ones are used */ + node_copy.remove_property_recursively (X_("id")); - /* 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 { - boost::shared_ptr route (XMLRouteFactory (node_copy, 3000)); + /* 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) { error << _("Session: cannot create track/bus from template description") << endmsg; @@ -1939,9 +2065,9 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template picks up the configuration of the route. During session loading this normally happens in a different way. */ - + Glib::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - + IOChange change (IOChange::Type (IOChange::ConfigurationChanged | IOChange::ConnectionsChanged)); change.after = route->input()->n_ports(); route->input()->changed (change, this); @@ -1970,21 +2096,26 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template out: if (!ret.empty()) { - add_routes (ret, true); + add_routes (ret, true, true, true); + IO::enable_connecting (); } return ret; } void -Session::add_routes (RouteList& new_routes, bool save) +Session::add_routes (RouteList& new_routes, bool input_auto_connect, bool output_auto_connect, bool save) { + ChanCount existing_inputs; + ChanCount existing_outputs; + + count_existing_track_channels (existing_inputs, existing_outputs); + { RCUWriter writer (routes); - boost::shared_ptr r = writer.get_copy (); + 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, @@ -2026,21 +2157,28 @@ Session::add_routes (RouteList& new_routes, bool save) boost::shared_ptr mt = boost::dynamic_pointer_cast (tr); if (mt) { mt->StepEditStatusChange.connect_same_thread (*this, boost::bind (&Session::step_edit_status_change, this, _1)); + mt->output()->changed.connect_same_thread (*this, boost::bind (&Session::midi_output_change_handler, this, _1, _2, boost::weak_ptr(mt))); } } + + if (input_auto_connect || output_auto_connect) { + auto_connect_route (r, existing_inputs, existing_outputs, true, input_auto_connect); + } } 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_out, - (Config->get_listen_position() == AfterFaderListen ? PostFader : PreFader), - false, false); + { + Glib::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 (); + } } } @@ -2104,7 +2242,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); } } @@ -2115,22 +2254,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, true, true); - } + sender->add_aux_send (dest, before); graph_reordered (); } @@ -2138,7 +2284,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; } @@ -2146,7 +2292,7 @@ Session::remove_route (boost::shared_ptr route) { RCUWriter writer (routes); - boost::shared_ptr rs = writer.get_copy (); + boost::shared_ptr rs = writer.get_copy (); rs->remove (route); @@ -2160,13 +2306,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 (); } @@ -2190,7 +2329,7 @@ Session::remove_route (boost::shared_ptr route) (*i)->remove_processor (s); } } - } + } boost::shared_ptr mt = boost::dynamic_pointer_cast (route); if (mt && mt->step_editing()) { @@ -2199,7 +2338,7 @@ Session::remove_route (boost::shared_ptr route) } } - update_latency_compensation (false, false); + update_latency_compensation (); set_dirty(); /* Re-sort routes to remove the graph's current references to the one that is @@ -2207,7 +2346,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 */ @@ -2245,7 +2386,7 @@ Session::route_listen_changed (void* /*src*/, boost::weak_ptr wpr) return; } - if (route->listening()) { + if (route->listening_via_monitor ()) { if (Config->get_exclusive_solo()) { /* new listen: disable all other listen */ @@ -2253,7 +2394,7 @@ Session::route_listen_changed (void* /*src*/, boost::weak_ptr wpr) 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()) { continue; - } + } (*i)->set_listen (false, this); } } @@ -2264,6 +2405,8 @@ Session::route_listen_changed (void* /*src*/, boost::weak_ptr wpr) _listen_cnt--; } + + update_route_solo_state (); } void Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr wpr) @@ -2275,29 +2418,31 @@ Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr wpr) error << string_compose (_("programming error: %1"), X_("invalid route weak ptr passed to route_solo_changed")) << endmsg; return; } - - bool send_changed = false; - - if (route->solo_isolated()) { - if (_solo_isolated_cnt == 0) { - send_changed = true; - } - _solo_isolated_cnt++; - } else if (_solo_isolated_cnt > 0) { - _solo_isolated_cnt--; - if (_solo_isolated_cnt == 0) { - send_changed = true; - } - } - if (send_changed) { - IsolatedChanged (); /* EMIT SIGNAL */ - } + bool send_changed = false; + + if (route->solo_isolated()) { + if (_solo_isolated_cnt == 0) { + send_changed = true; + } + _solo_isolated_cnt++; + } else if (_solo_isolated_cnt > 0) { + _solo_isolated_cnt--; + if (_solo_isolated_cnt == 0) { + send_changed = true; + } + } + + if (send_changed) { + IsolatedChanged (); /* EMIT SIGNAL */ + } } - + 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; @@ -2305,17 +2450,13 @@ 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 (); + assert (route); - if (!route) { - /* should not happen */ - error << string_compose (_("programming error: %1"), X_("invalid route weak ptr passed to route_solo_changed")) << endmsg; - return; - } - boost::shared_ptr r = routes.reader (); int32_t delta; @@ -2324,43 +2465,78 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p } else { 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) { @@ -2369,6 +2545,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 @@ -2376,6 +2554,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); } @@ -2401,7 +2580,7 @@ Session::update_route_solo_state (boost::shared_ptr r) something_soloed = true; } - if (!(*i)->is_hidden() && (*i)->listening()) { + if (!(*i)->is_hidden() && (*i)->listening_via_monitor()) { if (Config->get_solo_control_is_listen_control()) { listeners++; } else { @@ -2427,7 +2606,7 @@ Session::update_route_solo_state (boost::shared_ptr r) } } -boost::shared_ptr +boost::shared_ptr Session::get_routes_with_internal_returns() const { boost::shared_ptr r = routes.reader (); @@ -2445,20 +2624,76 @@ bool Session::io_name_is_legal (const std::string& name) { boost::shared_ptr r = routes.reader (); - + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { if ((*i)->name() == name) { return false; } - + if ((*i)->has_io_processor_named (name)) { return false; } } - + 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) { @@ -2487,6 +2722,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) { @@ -2528,6 +2778,8 @@ Session::playlist_region_added (boost::weak_ptr w) ops.push_back (Operations::region_fill); ops.push_back (Operations::fill_selection); ops.push_back (Operations::create_region); + ops.push_back (Operations::region_copy); + ops.push_back (Operations::fixed_time_region_copy); ops.sort (); /* See if any of the current operations match the ones that we want */ @@ -2551,15 +2803,15 @@ Session::maybe_update_session_range (framepos_t a, framepos_t b) } if (_session_range_location == 0) { - + add_session_range_location (a, b); - + } else { - + if (a < _session_range_location->start()) { _session_range_location->set_start (a); } - + if (b > _session_range_location->end()) { _session_range_location->set_end (b); } @@ -2574,6 +2826,14 @@ Session::playlist_ranges_moved (list > const & ran } } +void +Session::playlist_regions_extended (list > const & ranges) +{ + for (list >::const_iterator i = ranges.begin(); i != ranges.end(); ++i) { + maybe_update_session_range (i->from, i->to); + } +} + /* Region management */ boost::shared_ptr @@ -2609,31 +2869,25 @@ Session::destroy_sources (list > srcs) RegionFactory::get_regions_using_source (*s, relevant_regions); } - cerr << "There are " << relevant_regions.size() << " using " << srcs.size() << " sources" << endl; - for (set >::iterator r = relevant_regions.begin(); r != relevant_regions.end(); ) { set >::iterator tmp; tmp = r; ++tmp; - cerr << "Cleanup " << (*r)->name() << " UC = " << (*r).use_count() << endl; - playlists->destroy_region (*r); RegionFactory::map_remove (*r); (*r)->drop_sources (); (*r)->drop_references (); - cerr << "\tdone UC = " << (*r).use_count() << endl; - relevant_regions.erase (r); r = tmp; } for (list >::iterator s = srcs.begin(); s != srcs.end(); ) { - + { Glib::Mutex::Lock ls (source_lock); /* remove from the main source list */ @@ -2660,7 +2914,7 @@ Session::remove_last_capture () if (!tr) { continue; } - + list >& l = tr->last_capture_sources(); if (!l.empty()) { @@ -2696,21 +2950,35 @@ 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; - + if ((afs = boost::dynamic_pointer_cast(source)) != 0) { if (Config->get_auto_analyse_audio()) { Analyser::queue_source_for_analysis (source, false); } } - } + + source->DropReferences.connect_same_thread (*this, boost::bind (&Session::remove_source, this, boost::weak_ptr (source))); + } } void Session::remove_source (boost::weak_ptr src) { + if (_state_of_the_state & Deletion) { + return; + } + SourceMap::iterator i; boost::shared_ptr source = src.lock(); @@ -2722,12 +2990,11 @@ Session::remove_source (boost::weak_ptr src) Glib::Mutex::Lock lm (source_lock); if ((i = sources.find (source->id())) != sources.end()) { - cerr << "Removing source " << source->name() << endl; 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. @@ -2895,7 +3162,7 @@ Session::change_source_path_by_name (string path, string oldname, string newname * (e.g. as returned by new_*_source_name) */ string -Session::new_source_path_from_name (DataType type, const string& name, bool as_stub) +Session::new_source_path_from_name (DataType type, const string& name) { assert(name.find("/") == string::npos); @@ -2903,9 +3170,9 @@ Session::new_source_path_from_name (DataType type, const string& name, bool as_s sys::path p; if (type == DataType::AUDIO) { - p = (as_stub ? sdir.sound_stub_path() : sdir.sound_path()); + p = sdir.sound_path(); } else if (type == DataType::MIDI) { - p = (as_stub ? sdir.midi_stub_path() : sdir.midi_path()); + p = sdir.midi_path(); } else { error << "Unknown source type, unable to create file path" << endmsg; return ""; @@ -2985,16 +3252,14 @@ 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_stubs = sdir.sound_stub_path().to_string(); /* note that we search *without* the extension so that - we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf" + we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf" in the event that this new name is required for a file format change. */ - if (matching_unsuffixed_filename_exists_in (spath, buf) || - matching_unsuffixed_filename_exists_in (spath_stubs, buf)) { + if (matching_unsuffixed_filename_exists_in (spath, buf)) { existing++; break; } @@ -3012,16 +3277,16 @@ Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t cha throw failed_constructor(); } } - + return Glib::path_get_basename (buf); } /** Create a new within-session audio source */ boost::shared_ptr -Session::create_audio_source_for_session (size_t n_chans, string const & n, uint32_t chan, bool destructive, bool as_stub) +Session::create_audio_source_for_session (size_t n_chans, string const & n, uint32_t chan, bool destructive) { const string name = new_audio_source_name (n, n_chans, chan, destructive); - const string path = new_source_path_from_name(DataType::AUDIO, name, as_stub); + const string path = new_source_path_from_name(DataType::AUDIO, name); return boost::dynamic_pointer_cast ( SourceFactory::createWritable (DataType::AUDIO, *this, path, string(), destructive, frame_rate())); @@ -3078,9 +3343,9 @@ Session::new_midi_source_name (const string& base) /** Create a new within-session MIDI source */ boost::shared_ptr -Session::create_midi_source_for_session (Track* track, string const & n, bool as_stub) +Session::create_midi_source_for_session (Track* track, string const & n) { - /* try to use the existing write source for the track, to keep numbering sane + /* try to use the existing write source for the track, to keep numbering sane */ if (track) { @@ -3089,7 +3354,7 @@ Session::create_midi_source_for_session (Track* track, string const & n, bool as */ list > l = track->steal_write_sources (); - + if (!l.empty()) { assert (boost::dynamic_pointer_cast (l.front())); return boost::dynamic_pointer_cast (l.front()); @@ -3097,7 +3362,7 @@ Session::create_midi_source_for_session (Track* track, string const & n, bool as } const string name = new_midi_source_name (n); - const string path = new_source_path_from_name (DataType::MIDI, name, as_stub); + const string path = new_source_path_from_name (DataType::MIDI, name); return boost::dynamic_pointer_cast ( SourceFactory::createWritable ( @@ -3158,12 +3423,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 */ } @@ -3187,7 +3449,7 @@ Session::cancel_audition () bool Session::RoutePublicOrderSorter::operator() (boost::shared_ptr a, boost::shared_ptr b) { - if (a->is_monitor()) { + if (a->is_monitor()) { return true; } if (b->is_monitor()) { @@ -3270,7 +3532,7 @@ Session::available_capture_duration () if (_total_free_4k_blocks * scale > (double) max_framecnt) { return max_framecnt; } - + return (framecnt_t) floor (_total_free_4k_blocks * scale); } @@ -3333,7 +3595,7 @@ Session::tempo_map_changed (const PropertyChange&) playlists->update_after_tempo_map_change (); _locations->apply (*this, &Session::update_locations_after_tempo_map_change); - + set_dirty (); } @@ -3402,6 +3664,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 () { @@ -3434,6 +3716,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) { @@ -3466,6 +3760,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) { @@ -3600,7 +3902,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; @@ -3637,19 +3941,13 @@ 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) { for (x = 0; x < 99999; ++x) { snprintf (buf, sizeof(buf), "%s/%s-%d-bounce-%" PRIu32 "%s", sound_dir.c_str(), playlist->name().c_str(), chan_n, x+1, ext.c_str()); - if (access (buf, F_OK) != 0) { + if (!Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) { break; } } @@ -3672,13 +3970,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; @@ -3696,7 +3995,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; } @@ -3737,13 +4036,13 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, /* construct a region to represent the bounced material */ PropertyList plist; - + plist.add (Properties::start, 0); plist.add (Properties::length, srcs.front()->length(srcs.front()->timeline_position())); plist.add (Properties::name, region_name_from_path (srcs.front()->name(), true)); - + result = RegionFactory::create (srcs, plist); - + } out: @@ -3771,7 +4070,7 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, if (need_block_size_reset) { track.set_block_size (get_block_size()); } - + unblock_processing (); return result; @@ -3793,45 +4092,18 @@ BufferSet& Session::get_silent_buffers (ChanCount count) { return ProcessThread::get_silent_buffers (count); -#if 0 - assert(_silent_buffers->available() >= count); - _silent_buffers->set_count(count); - - for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) { - for (size_t i= 0; i < count.get(*t); ++i) { - _silent_buffers->get(*t, i).clear(); - } - } - - return *_silent_buffers; -#endif } BufferSet& Session::get_scratch_buffers (ChanCount count) { return ProcessThread::get_scratch_buffers (count); -#if 0 - if (count != ChanCount::ZERO) { - assert(_scratch_buffers->available() >= count); - _scratch_buffers->set_count(count); - } else { - _scratch_buffers->set_count (_scratch_buffers->available()); - } - - return *_scratch_buffers; -#endif } BufferSet& Session::get_mix_buffers (ChanCount count) { return ProcessThread::get_mix_buffers (count); -#if 0 - assert(_mix_buffers->available() >= count); - _mix_buffers->set_count(count); - return *_mix_buffers; -#endif } uint32_t @@ -3914,7 +4186,7 @@ Session::update_have_rec_enabled_track () if (tr && tr->record_enabled ()) { break; } - + ++i; } @@ -3930,22 +4202,10 @@ Session::update_have_rec_enabled_track () void Session::listen_position_changed () { - Placement p; - - switch (Config->get_listen_position()) { - case AfterFaderListen: - p = PostFader; - break; - - case PreFaderListen: - p = PreFader; - break; - } - boost::shared_ptr r = routes.reader (); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - (*i)->put_monitor_send_at (p); + (*i)->listen_position_changed (); } } @@ -3961,18 +4221,32 @@ 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_property_changed (RouteGroup* rg) +{ + RouteGroupPropertyChanged (rg); /* EMIT SIGNAL */ +} + +/** 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) +{ + RouteAddedToRouteGroup (rg, r); +} + +/** Called when a route is removed from one of our route groups */ void -Session::route_group_changed () +Session::route_removed_from_route_group (RouteGroup* rg, boost::weak_ptr r) { - RouteGroupChanged (); /* EMIT SIGNAL */ + RouteRemovedFromRouteGroup (rg, r); } vector Session::get_available_sync_options () const { vector ret; - + ret.push_back (JACK); ret.push_back (MTC); ret.push_back (MIDIClock); @@ -3991,12 +4265,12 @@ Session::get_routes_with_regions_at (framepos_t const p) const if (!tr) { continue; } - + boost::shared_ptr pl = tr->playlist (); if (!pl) { continue; } - + if (pl->has_region_at (p)) { rl->push_back (*i); } @@ -4076,7 +4350,7 @@ Session::step_edit_status_change (bool yn) } } - + void Session::start_time_changed (framepos_t old) { @@ -4088,10 +4362,10 @@ Session::start_time_changed (framepos_t old) if (s == 0) { return; } - + Location* l = _locations->auto_loop_location (); - if (l->start() == old) { + if (l && l->start() == old) { l->set_start (s->start(), true); } } @@ -4107,10 +4381,10 @@ Session::end_time_changed (framepos_t old) if (s == 0) { return; } - + Location* l = _locations->auto_loop_location (); - if (l->end() == old) { + if (l && l->end() == old) { l->set_end (s->end(), true); } } @@ -4118,35 +4392,32 @@ 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().to_string()); break; case DataType::MIDI: - search_path = _session_dir->midi_path().to_string(); + s.push_back (_session_dir->midi_path().to_string()); 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().to_string()); break; case DataType::MIDI: - search_path += sdir.midi_path().to_string(); + s.push_back (sdir.midi_path().to_string()); break; } } } - - /* now add user-specified locations + + /* now check the explicit (possibly user-specified) search path */ vector dirs; @@ -4161,11 +4432,29 @@ 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; } @@ -4191,7 +4480,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::sys::inodes_same (*i, path)) { return; } } @@ -4201,7 +4496,7 @@ Session::ensure_search_path_includes (const string& path, DataType type) } search_path += path; - + switch (type) { case DataType::AUDIO: config.set_audio_search_path (search_path); @@ -4212,10 +4507,10 @@ Session::ensure_search_path_includes (const string& path, DataType type) } } -Speakers& -Session::get_speakers() +boost::shared_ptr +Session::get_speakers() { - return *_speakers; + return _speakers; } list @@ -4235,23 +4530,217 @@ Session::unknown_processors () const return p; } -#ifdef HAVE_JACK_NEW_LATENCY void Session::update_latency (bool playback) { - DEBUG_TRACE (DEBUG::Latency, "JACK latency callback\n"); + DEBUG_TRACE (DEBUG::Latency, string_compose ("JACK latency callback: %1\n", (playback ? "PLAYBACK" : "CAPTURE"))); + + if (_state_of_the_state & (InitialConnecting|Deletion)) { + return; + } boost::shared_ptr r = routes.reader (); + framecnt_t max_latency = 0; + + 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()); + } + + /* compute actual latency values for the given direction and store them all in per-port + structures. this will also publish the same values (to JACK) so that computation of latency + for routes can consistently use public latency values. + */ + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + max_latency = max (max_latency, (*i)->set_private_port_latencies (playback)); + } - if (playback) { - /* reverse the list so that we work backwards from the last route to run to the first */ - reverse (r->begin(), r->end()); + /* because we latency compensate playback, our published playback latencies should + be the same for all output ports - all material played back by ardour has + the same latency, whether its caused by plugins or by latency compensation. since + these may differ from the values computed above, reset all playback port latencies + to the same value. + */ + + DEBUG_TRACE (DEBUG::Latency, string_compose ("Set public port latencies to %1\n", max_latency)); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->set_public_port_latencies (max_latency, playback); } + if (playback) { + + post_playback_latency (); + + } else { + + post_capture_latency (); + } + + DEBUG_TRACE (DEBUG::Latency, "JACK latency callback: DONE\n"); +} + +void +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) { - DEBUG_TRACE (DEBUG::Latency, string_compose ("------------- Working on latency for %1\n", (*i)->name())); - (*i)->set_latency_ranges (playback); - DEBUG_TRACE (DEBUG::Latency, string_compose ("------------- Done working on latency for %1\n\n", (*i)->name())); + (*i)->set_latency_compensation (_worst_track_latency); + } +} + +void +Session::post_capture_latency () +{ + set_worst_capture_latency (); + + /* reflect any changes in capture latencies into capture offsets + */ + + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->set_capture_offset (); + } + } +} + +void +Session::initialize_latencies () +{ + { + Glib::Mutex::Lock lm (_engine.process_lock()); + update_latency (false); + update_latency (true); } + + set_worst_io_latencies (); +} + +void +Session::set_worst_io_latencies () +{ + set_worst_playback_latency (); + set_worst_capture_latency (); +} + +void +Session::set_worst_playback_latency () +{ + if (_state_of_the_state & (InitialConnecting|Deletion)) { + return; + } + + _worst_output_latency = 0; + + if (!_engine.connected()) { + return; + } + + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + _worst_output_latency = max (_worst_output_latency, (*i)->output()->latency()); + } + + DEBUG_TRACE (DEBUG::Latency, string_compose ("Worst output latency: %1\n", _worst_output_latency)); +} + +void +Session::set_worst_capture_latency () +{ + if (_state_of_the_state & (InitialConnecting|Deletion)) { + return; + } + + _worst_input_latency = 0; + + if (!_engine.connected()) { + return; + } + + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + _worst_input_latency = max (_worst_input_latency, (*i)->input()->latency()); + } + + DEBUG_TRACE (DEBUG::Latency, string_compose ("Worst input latency: %1\n", _worst_input_latency)); +} + +void +Session::update_latency_compensation (bool force_whole_graph) +{ + bool some_track_latency_changed = false; + + if (_state_of_the_state & (InitialConnecting|Deletion)) { + return; + } + + DEBUG_TRACE(DEBUG::Latency, "---------------------------- update latency compensation\n\n"); + + _worst_track_latency = 0; + + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + if (!(*i)->is_hidden() && ((*i)->active())) { + framecnt_t tl; + if ((*i)->signal_latency () != (tl = (*i)->update_signal_latency ())) { + some_track_latency_changed = true; + } + _worst_track_latency = max (tl, _worst_track_latency); + } + } + + DEBUG_TRACE (DEBUG::Latency, string_compose ("worst signal processing latency: %1 (changed ? %2)\n", _worst_track_latency, + (some_track_latency_changed ? "yes" : "no"))); + + 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 +{ + return ntracks() + nbusses() + 1; } -#endif