X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession.cc;h=4097335f5b811138e035e9f654f89cdc4523fec3;hb=7fc3b0c34c552d7be862897bd0aaa542453e9973;hp=2e93d5d167ce2b60cc655cb088c132d39aef2430;hpb=9e3299f97da874a48f67dc5ff0e0f87a6a54768a;p=ardour.git diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 2e93d5d167..4097335f5b 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -36,12 +36,12 @@ #include #include "pbd/basename.h" -#include "pbd/boost_debug.h" #include "pbd/convert.h" #include "pbd/convert.h" #include "pbd/error.h" #include "pbd/file_utils.h" #include "pbd/md5.h" +#include "pbd/pthread_utils.h" #include "pbd/search_path.h" #include "pbd/stacktrace.h" #include "pbd/stl_delete.h" @@ -58,6 +58,7 @@ #include "ardour/audioengine.h" #include "ardour/audiofilesource.h" #include "ardour/auditioner.h" +#include "ardour/boost_debug.h" #include "ardour/buffer_manager.h" #include "ardour/buffer_set.h" #include "ardour/bundle.h" @@ -71,7 +72,9 @@ #include "ardour/engine_state_controller.h" #endif #include "ardour/filename_extensions.h" +#include "ardour/gain_control.h" #include "ardour/graph.h" +#include "ardour/luabindings.h" #include "ardour/midiport_manager.h" #include "ardour/scene_changer.h" #include "ardour/midi_patch_manager.h" @@ -98,6 +101,7 @@ #include "ardour/source_factory.h" #include "ardour/speakers.h" #include "ardour/tempo.h" +#include "ardour/ticker.h" #include "ardour/track.h" #include "ardour/user_bundle.h" #include "ardour/utils.h" @@ -105,6 +109,8 @@ #include "midi++/port.h" #include "midi++/mmc.h" +#include "LuaBridge/LuaBridge.h" + #include "i18n.h" #include @@ -121,11 +127,13 @@ using namespace PBD; bool Session::_disable_all_loaded_plugins = false; bool Session::_bypass_all_loaded_plugins = false; +guint Session::_name_id_counter = 0; PBD::Signal1 Session::AudioEngineSetupRequired; PBD::Signal1 Session::Dialog; PBD::Signal0 Session::AskAboutPendingState; PBD::Signal2 Session::AskAboutSampleRateMismatch; +PBD::Signal2 Session::NotifyAboutSampleRateMismatch; PBD::Signal0 Session::SendFeedback; PBD::Signal3 Session::MissingFile; @@ -161,8 +169,8 @@ Session::Session (AudioEngine &eng, , _bounce_processing_active (false) , waiting_for_sync_offset (false) , _base_frame_rate (0) - , _current_frame_rate (0) , _nominal_frame_rate (0) + , _current_frame_rate (0) , transport_sub_state (0) , _record_status (Disabled) , _transport_frame (0) @@ -183,6 +191,7 @@ Session::Session (AudioEngine &eng, , _worst_track_latency (0) , _have_captured (false) , _non_soloed_outs_muted (false) + , _listening (false) , _listen_cnt (0) , _solo_isolated_cnt (0) , _writable (false) @@ -199,8 +208,8 @@ Session::Session (AudioEngine &eng, , post_export_sync (false) , post_export_position (0) , _exporting (false) - , _export_started (false) , _export_rolling (false) + , _export_preroll (0) , _pre_export_mmc_enabled (false) , _name (snapshot_name) , _is_new (true) @@ -225,6 +234,9 @@ Session::Session (AudioEngine &eng, , pending_locate_flush (false) , pending_abort (false) , pending_auto_loop (false) + , _mempool ("Session", 1048576) + , lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool)) + , _n_lua_scripts (0) , _butler (new Butler (*this)) , _post_transport_work (0) , cumulative_rf_motion (0) @@ -233,6 +245,7 @@ Session::Session (AudioEngine &eng, , _ignore_skips_updates (false) , _rt_thread_active (false) , _rt_emit_pending (false) + , _ac_thread_active (false) , step_speed (0) , outbound_mtc_timecode_frame (0) , next_quarter_frame_to_send (-1) @@ -294,6 +307,7 @@ Session::Session (AudioEngine &eng, , _speakers (new Speakers) , _order_hint (-1) , ignore_route_processor_changes (false) + , midi_clock (0) , _scene_changer (0) , _midi_ports (0) , _mmc (0) @@ -303,8 +317,15 @@ Session::Session (AudioEngine &eng, pthread_mutex_init (&_rt_emit_mutex, 0); pthread_cond_init (&_rt_emit_cond, 0); + pthread_mutex_init (&_auto_connect_mutex, 0); + pthread_cond_init (&_auto_connect_cond, 0); + + init_name_id_counter (1); // reset for new sessions, start at 1 + pre_engine_init (fullpath); + setup_lua (); + if (_is_new) { Stateful::loading_state_version = CURRENT_SESSION_FILE_VERSION; @@ -312,7 +333,7 @@ Session::Session (AudioEngine &eng, #ifdef USE_TRACKS_CODE_FEATURES sr = EngineStateController::instance()->get_current_sample_rate(); #endif - if (ensure_engine (sr)) { + if (ensure_engine (sr, true)) { destroy (); throw SessionException (_("Cannot connect to audio/midi engine")); } @@ -360,13 +381,14 @@ Session::Session (AudioEngine &eng, */ if (state_tree) { - const XMLProperty* prop; - if ((prop = state_tree->root()->property (X_("sample-rate"))) != 0) { + XMLProperty const * prop; + XMLNode const * root (state_tree->root()); + if ((prop = root->property (X_("sample-rate"))) != 0) { sr = atoi (prop->value()); } } - if (ensure_engine (sr)) { + if (ensure_engine (sr, false)) { destroy (); throw SessionException (_("Cannot connect to audio/midi engine")); } @@ -394,6 +416,7 @@ Session::Session (AudioEngine &eng, EndTimeChanged.connect_same_thread (*this, boost::bind (&Session::end_time_changed, this, _1)); emit_thread_start (); + auto_connect_thread_start (); /* hook us up to the engine since we are now completely constructed */ @@ -459,8 +482,26 @@ Session::~Session () destroy (); } +unsigned int +Session::next_name_id () +{ + return g_atomic_int_add (&_name_id_counter, 1); +} + +unsigned int +Session::name_id_counter () +{ + return g_atomic_int_get (&_name_id_counter); +} + +void +Session::init_name_id_counter (guint n) +{ + g_atomic_int_set (&_name_id_counter, n); +} + int -Session::ensure_engine (uint32_t desired_sample_rate) +Session::ensure_engine (uint32_t desired_sample_rate, bool isnew) { if (_engine.current_backend() == 0) { /* backend is unknown ... */ @@ -468,6 +509,8 @@ Session::ensure_engine (uint32_t desired_sample_rate) if (r.get_value_or (-1) != 0) { return -1; } + } else if (!isnew && _engine.running() && _engine.sample_rate () == desired_sample_rate) { + /* keep engine */ } else if (_engine.setup_required()) { /* backend is known, but setup is needed */ boost::optional r = AudioEngineSetupRequired (desired_sample_rate); @@ -480,8 +523,7 @@ Session::ensure_engine (uint32_t desired_sample_rate) } } - /* at this point the engine should be running - */ + /* at this point the engine should be running */ if (!_engine.running()) { return -1; @@ -519,6 +561,7 @@ Session::immediately_post_engine () } try { + LocaleGuard lg; BootMessage (_("Set up LTC")); setup_ltc (); BootMessage (_("Set up Click")); @@ -553,8 +596,12 @@ Session::destroy () _state_of_the_state = StateOfTheState (CannotSave|Deletion); + /* stop autoconnecting */ + auto_connect_thread_terminate (); + /* disconnect from any and all signals that we are connected to */ + Port::PortSignalDrop (); /* EMIT SIGNAL */ drop_connections (); /* shutdown control surface protocols while we still have ports @@ -588,8 +635,19 @@ Session::destroy () delete state_tree; state_tree = 0; - /* reset dynamic state version back to default */ + // unregister all lua functions, drop held references (if any) + (*_lua_cleanup)(); + lua.do_command ("Session = nil"); + delete _lua_run; + delete _lua_add; + delete _lua_del; + delete _lua_list; + delete _lua_save; + delete _lua_load; + delete _lua_cleanup; + lua.collect_garbage (); + /* reset dynamic state version back to default */ Stateful::loading_state_version = 0; _butler->drop_references (); @@ -676,6 +734,9 @@ Session::destroy () pthread_cond_destroy (&_rt_emit_cond); pthread_mutex_destroy (&_rt_emit_mutex); + pthread_cond_destroy (&_auto_connect_cond); + pthread_mutex_destroy (&_auto_connect_mutex); + delete _scene_changer; _scene_changer = 0; delete midi_control_ui; midi_control_ui = 0; @@ -683,13 +744,48 @@ Session::destroy () delete _midi_ports; _midi_ports = 0; delete _locations; _locations = 0; + delete midi_clock; delete _tempo_map; + /* clear event queue, the session is gone, nobody is interested in + * those anymore, but they do leak memory if not removed + */ + while (!immediate_events.empty ()) { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + SessionEvent *ev = immediate_events.front (); + DEBUG_TRACE (DEBUG::SessionEvents, string_compose ("Drop event: %1\n", enum_2_string (ev->type))); + immediate_events.pop_front (); + bool remove = true; + bool del = true; + switch (ev->type) { + case SessionEvent::AutoLoop: + case SessionEvent::AutoLoopDeclick: + case SessionEvent::Skip: + case SessionEvent::PunchIn: + case SessionEvent::PunchOut: + case SessionEvent::StopOnce: + case SessionEvent::RangeStop: + case SessionEvent::RangeLocate: + remove = false; + del = false; + break; + case SessionEvent::RealTimeOperation: + process_rtop (ev); + del = false; + default: + break; + } + if (remove) { + del = del && !_remove_event (ev); + } + if (del) { + delete ev; + } + } + DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n"); -#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - boost_debug_list_ptrs (); -#endif + BOOST_SHOW_POINTERS (); } void @@ -732,8 +828,12 @@ void Session::setup_click () { _clicking = false; + + boost::shared_ptr gl (new AutomationList (Evoral::Parameter (GainAutomation))); + boost::shared_ptr gain_control = boost::shared_ptr (new GainControl (*this, Evoral::Parameter(GainAutomation), gl)); + _click_io.reset (new ClickIO (*this, X_("Click"))); - _click_gain.reset (new Amp (*this)); + _click_gain.reset (new Amp (*this, _("Fader"), gain_control, true)); _click_gain->activate (); if (state_tree) { setup_click_state (state_tree->root()); @@ -1017,11 +1117,17 @@ Session::remove_monitor_section () } remove_route (_monitor_out); + if (_state_of_the_state & Deletion) { + return; + } + auto_connect_master_bus (); if (auditioner) { auditioner->connect (); } + + Config->ParameterChanged ("use-monitor-bus"); } void @@ -1039,9 +1145,8 @@ Session::add_monitor_section () return; } -#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - // boost_debug_shared_ptr_mark_interesting (r.get(), "Route"); -#endif + BOOST_MARK_ROUTE(r); + try { Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); r->input()->ensure_io (_master_out->output()->n_ports(), false, this); @@ -1092,7 +1197,7 @@ Session::add_monitor_section () /* 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_auto_connect_standard_busses () || Profile->get_mixbus ()) && !_monitor_out->output()->connected ()) { if (!Config->get_monitor_bus_preferred_bundle().empty()) { @@ -1173,6 +1278,7 @@ Session::add_monitor_section () if (auditioner) { auditioner->connect (); } + Config->ParameterChanged ("use-monitor-bus"); } void @@ -1824,6 +1930,19 @@ Session::enable_record () } } +void +Session::set_all_tracks_record_enabled (bool enable ) +{ + 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_record_enabled (enable, Controllable::NoGroup); + } + } +} + + void Session::disable_record (bool rt_context, bool force) { @@ -1963,7 +2082,12 @@ Session::set_frame_rate (framecnt_t frames_per_second) here. */ - _base_frame_rate = frames_per_second; + if (_base_frame_rate == 0) { + _base_frame_rate = frames_per_second; + } + else if (_base_frame_rate != frames_per_second && frames_per_second != _nominal_frame_rate) { + NotifyAboutSampleRateMismatch (_base_frame_rate, frames_per_second); + } _nominal_frame_rate = frames_per_second; sync_time_vars(); @@ -2086,16 +2210,18 @@ Session::resort_routes () } #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())); + if (DEBUG_ENABLED(DEBUG::Graph)) { + 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()); + const Route::FedBy& fb ((*i)->fed_by()); - for (Route::FedBy::const_iterator f = fb.begin(); f != fb.end(); ++f) { - boost::shared_ptr sf = f->r.lock(); - if (sf) { - DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 (sends only ? %2)\n", sf->name(), f->sends_only)); + for (Route::FedBy::const_iterator f = fb.begin(); f != fb.end(); ++f) { + boost::shared_ptr sf = f->r.lock(); + if (sf) { + DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 (sends only ? %2)\n", sf->name(), f->sends_only)); + } } } } @@ -2297,7 +2423,7 @@ Session::default_track_name_pattern (DataType t) */ list > 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) + TrackMode mode, RouteGroup* route_group, uint32_t how_many, string name_template, Plugin::PresetRecord* pset) { string track_name; uint32_t track_id = 0; @@ -2323,11 +2449,14 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, boost: goto failed; } + if (Profile->get_mixbus ()) { + track->set_strict_io (true); + } + track->use_new_diskstream(); -#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - // boost_debug_shared_ptr_mark_interesting (track.get(), "Track"); -#endif + BOOST_MARK_TRACK (track); + { Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); if (track->input()->ensure_io (input, false, this)) { @@ -2385,6 +2514,9 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, boost: if (instrument) { for (RouteList::iterator r = new_routes.begin(); r != new_routes.end(); ++r) { PluginPtr plugin = instrument->load (*this); + if (pset) { + plugin->load_preset (*pset); + } boost::shared_ptr p (new PluginInsert (*this, plugin)); (*r)->add_processor (p, PreFader); @@ -2395,154 +2527,121 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, boost: return ret; } -void -Session::midi_output_change_handler (IOChange change, void * /*src*/, boost::weak_ptr wmt) +RouteList +Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name_template, boost::shared_ptr instrument, Plugin::PresetRecord* pset) { - 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. - */ + string bus_name; + uint32_t bus_id = 0; + string port; + RouteList ret; - ChanCount dummy; + bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Midi Bus"); - auto_connect_route (midi_track, dummy, dummy, false, false, ChanCount(), change.before); - } -} + while (how_many) { + if (!find_route_name (name_template.empty () ? _("Midi Bus") : name_template, ++bus_id, bus_name, use_number)) { + error << "cannot find name for new midi bus" << endmsg; + goto failure; + } -/** @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 (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; - } + try { + boost::shared_ptr bus (new Route (*this, bus_name, Route::Flag(0), DataType::AUDIO)); // XXX Editor::add_routes is not ready for ARDOUR::DataType::MIDI - Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock (), Glib::Threads::NOT_LOCK); + if (bus->init ()) { + goto failure; + } - if (with_lock) { - lm.acquire (); - } + if (Profile->get_mixbus ()) { + bus->set_strict_io (true); + } - /* 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 - first audio output of the route will have the same physical - port number). Otherwise just use the lowest input or output - offset possible. - */ + BOOST_MARK_ROUTE(bus); - DEBUG_TRACE (DEBUG::Graph, - string_compose("Auto-connect: existing in = %1 out = %2\n", - existing_inputs, existing_outputs)); + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - const bool in_out_physical = - (Config->get_input_auto_connect() & AutoConnectPhysical) - && (Config->get_output_auto_connect() & AutoConnectPhysical) - && connect_inputs; + if (bus->input()->ensure_io (ChanCount(DataType::MIDI, 1), false, this)) { + error << _("cannot configure new midi bus input") << endmsg; + goto failure; + } - const ChanCount in_offset = in_out_physical - ? ChanCount::max(existing_inputs, existing_outputs) - : existing_inputs; - const ChanCount out_offset = in_out_physical - ? ChanCount::max(existing_inputs, existing_outputs) - : existing_outputs; + if (bus->output()->ensure_io (ChanCount(DataType::MIDI, 1), false, this)) { + error << _("cannot configure new midi bus output") << endmsg; + goto failure; + } + } - for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) { - vector physinputs; - vector physoutputs; + if (route_group) { + route_group->add (bus); + } + if (Config->get_remote_model() == UserOrdered) { + bus->set_remote_control_id (next_control_id()); + } - _engine.get_physical_outputs (*t, physoutputs); - _engine.get_physical_inputs (*t, physinputs); + ret.push_back (bus); + RouteAddedOrRemoved (true); /* EMIT SIGNAL */ + ARDOUR::GUIIdle (); + } - if (!physinputs.empty() && connect_inputs) { - uint32_t nphysical_in = physinputs.size(); + catch (failed_constructor &err) { + error << _("Session: could not create new audio route.") << endmsg; + goto failure; + } - DEBUG_TRACE (DEBUG::Graph, - string_compose("There are %1 physical inputs of type %2\n", - nphysical_in, *t)); + catch (AudioEngine::PortRegistrationFailure& pfe) { + error << pfe.what() << endmsg; + goto failure; + } - 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]; - } + --how_many; + } - DEBUG_TRACE (DEBUG::Graph, - string_compose("Connect route %1 IN to %2\n", - route->name(), port)); + failure: + if (!ret.empty()) { + StateProtector sp (this); + add_routes (ret, false, false, false); - if (!port.empty() && route->input()->connect (route->input()->ports().port(*t, i), port, this)) { - break; + if (instrument) { + for (RouteList::iterator r = ret.begin(); r != ret.end(); ++r) { + PluginPtr plugin = instrument->load (*this); + if (pset) { + plugin->load_preset (*pset); } - - ChanCount one_added (*t, 1); - existing_inputs += one_added; + boost::shared_ptr p (new PluginInsert (*this, plugin)); + (*r)->add_processor (p, PreFader); } } + } - if (!physoutputs.empty()) { - uint32_t nphysical_out = physoutputs.size(); - for (uint32_t i = output_start.get(*t); i < route->n_outputs().get(*t); ++i) { - string port; + return ret; - /* Waves Tracks: - * do not create new connections if we reached the limit of physical outputs - * in Multi Out mode - */ +} - if (!(Config->get_output_auto_connect() & AutoConnectMaster) && - ARDOUR::Profile->get_trx () && - existing_outputs.get(*t) == nphysical_out ) { - break; - } - if ((*t) == DataType::MIDI && (Config->get_output_auto_connect() & AutoConnectPhysical)) { - port = physoutputs[(out_offset.get(*t) + i) % nphysical_out]; - } else if ((*t) == DataType::AUDIO && (Config->get_output_auto_connect() & AutoConnectMaster)) { - /* 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(); - } - } +void +Session::midi_output_change_handler (IOChange change, void * /*src*/, boost::weak_ptr wmt) +{ + boost::shared_ptr midi_track (wmt.lock()); - DEBUG_TRACE (DEBUG::Graph, - string_compose("Connect route %1 OUT to %2\n", - route->name(), port)); + if (!midi_track) { + return; + } - if (!port.empty() && route->output()->connect (route->output()->ports().port(*t, i), port, this)) { - break; - } + if ((change.type & IOChange::ConfigurationChanged) && Config->get_output_auto_connect() != ManualConnect) { - ChanCount one_added (*t, 1); - existing_outputs += one_added; - } + 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. + */ + auto_connect_route (midi_track, false, ChanCount(), change.before); } } @@ -2704,8 +2803,6 @@ Session::reconnect_existing_routes (bool withLock, bool reconnect_master, bool r } } } - - //auto_connect_route (*rIter, inputs, outputs, false, reconnectIputs); } _master_out->output()->disconnect (this); @@ -2862,6 +2959,11 @@ Session::new_audio_track (int input_channels, int output_channels, TrackMode mod goto failed; } + if (Profile->get_mixbus ()) { + track->set_strict_io (true); + } + + if (ARDOUR::Profile->get_trx ()) { // TRACKS considers it's not a USE CASE, it's // a piece of behavior of the session model: @@ -2872,15 +2974,14 @@ Session::new_audio_track (int input_channels, int output_channels, TrackMode mod // 0 for Stereo Out mode // 0 Multi Out mode if (Config->get_output_auto_connect() & AutoConnectMaster) { - track->set_gain (dB_to_coefficient (0), 0); + track->set_gain (dB_to_coefficient (0), Controllable::NoGroup); } } track->use_new_diskstream(); -#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - // boost_debug_shared_ptr_mark_interesting (track.get(), "Track"); -#endif + BOOST_MARK_TRACK (track); + { Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); @@ -2971,9 +3072,12 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r goto failure; } -#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS - // boost_debug_shared_ptr_mark_interesting (bus.get(), "Route"); -#endif + if (Profile->get_mixbus ()) { + bus->set_strict_io (true); + } + + BOOST_MARK_ROUTE(bus); + { Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); @@ -3117,10 +3221,48 @@ Session::new_route_from_template (uint32_t how_many, XMLNode& node, const std::s 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")); + /* ForceIDRegeneration does not catch the following */ + XMLProperty const * role = (*i)->property (X_("role")); + XMLProperty const * type = (*i)->property (X_("type")); + if (role && role->value() == X_("Aux")) { + /* check if the target bus exists. + * we should not save aux-sends in templates. + */ + XMLProperty const * target = (*i)->property (X_("target")); + if (!target) { + (*i)->add_property ("type", "dangling-aux-send"); + continue; + } + boost::shared_ptr r = route_by_id (target->value()); + if (!r || boost::dynamic_pointer_cast(r)) { + (*i)->add_property ("type", "dangling-aux-send"); + continue; + } + } if (role && role->value() == X_("Listen")) { (*i)->remove_property (X_("bitslot")); } + else if (role && (role->value() == X_("Send") || role->value() == X_("Aux"))) { + char buf[32]; + Delivery::Role xrole; + uint32_t bitslot = 0; + xrole = Delivery::Role (string_2_enum (role->value(), xrole)); + std::string name = Send::name_and_id_new_send(*this, xrole, bitslot, false); + snprintf (buf, sizeof (buf), "%" PRIu32, bitslot); + (*i)->remove_property (X_("bitslot")); + (*i)->remove_property (X_("name")); + (*i)->add_property ("bitslot", buf); + (*i)->add_property ("name", name); + } + else if (type && type->value() == X_("return")) { + // Return::set_state() generates a new one + (*i)->remove_property (X_("bitslot")); + } + else if (type && type->value() == X_("port")) { + // PortInsert::set_state() handles the bitslot + (*i)->remove_property (X_("bitslot")); + (*i)->add_property ("ignore-name", "1"); + } } } @@ -3228,16 +3370,17 @@ Session::add_routes (RouteList& new_routes, bool input_auto_connect, bool output void Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool output_auto_connect) { - ChanCount existing_inputs; - ChanCount existing_outputs; + ChanCount existing_inputs; + ChanCount existing_outputs; uint32_t order = next_control_id(); + if (_order_hint > -1) { order = _order_hint; _order_hint = -1; } - count_existing_track_channels (existing_inputs, existing_outputs); + count_existing_track_channels (existing_inputs, existing_outputs); { RCUWriter writer (routes); @@ -3245,10 +3388,10 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool 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, - we will resort when done. - */ + * 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, + * we will resort when done. + */ if (!_monitor_out && IO::connecting_legal) { resort_routes_using (r); @@ -3260,10 +3403,10 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool boost::weak_ptr wpr (*x); boost::shared_ptr r (*x); - r->listen_changed.connect_same_thread (*this, boost::bind (&Session::route_listen_changed, this, _2, wpr)); - r->solo_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _3, wpr)); - r->solo_isolated_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, _1, wpr)); - r->mute_changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this, _1)); + r->listen_changed.connect_same_thread (*this, boost::bind (&Session::route_listen_changed, this, _1, wpr)); + r->solo_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _2, wpr)); + r->solo_isolated_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, wpr)); + r->mute_changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this)); 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)); @@ -3284,19 +3427,20 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool 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))); + 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); + auto_connect_route (r, input_auto_connect, ChanCount (), ChanCount (), existing_inputs, existing_outputs); + existing_inputs += r->n_inputs(); + existing_outputs += r->n_outputs(); } /* 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. - */ + reasonable defaults because they also affect the remote control + ID in most situations. + */ if (!r->has_order_key ()) { if (r->is_auditioner()) { @@ -3319,7 +3463,7 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool if ((*x)->is_monitor()) { /* relax */ } else if ((*x)->is_master()) { - /* relax */ + /* relax */ } else { (*x)->enable_monitor_send (); } @@ -3413,7 +3557,6 @@ Session::add_internal_send (boost::shared_ptr dest, boost::shared_ptr routes_to_remove) { @@ -3429,7 +3572,7 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) continue; } - (*iter)->set_solo (false, this); + (*iter)->set_solo (false, Controllable::NoGroup); rs->remove (*iter); @@ -3499,7 +3642,7 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) resort_routes (); #endif - if (_process_graph) { + if (_process_graph && !(_state_of_the_state & Deletion)) { _process_graph->clear_other_chain (); } @@ -3516,6 +3659,10 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) (*iter)->drop_references (); } + if (_state_of_the_state & Deletion) { + return; + } + Route::RemoteControlIDChange(); /* EMIT SIGNAL */ /* save the new state of the world */ @@ -3537,13 +3684,13 @@ Session::remove_route (boost::shared_ptr route) } void -Session::route_mute_changed (void* /*src*/) +Session::route_mute_changed () { set_dirty (); } void -Session::route_listen_changed (bool group_override, boost::weak_ptr wpr) +Session::route_listen_changed (Controllable::GroupControlDisposition group_override, boost::weak_ptr wpr) { boost::shared_ptr route = wpr.lock(); if (!route) { @@ -3554,18 +3701,32 @@ Session::route_listen_changed (bool group_override, boost::weak_ptr wpr) if (route->listening_via_monitor ()) { if (Config->get_exclusive_solo()) { - /* new listen: disable all other listen, except solo-grouped channels */ + RouteGroup* rg = route->route_group (); - bool leave_group_alone = (rg && rg->is_active() && rg->is_solo()); - if (group_override && rg) { - leave_group_alone = !leave_group_alone; - } + const bool group_already_accounted_for = route->use_group (group_override, &RouteGroup::is_solo); + boost::shared_ptr r = routes.reader (); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - if ((*i) == route || (*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner() || (leave_group_alone && ((*i)->route_group() == rg))) { + if ((*i) == route) { + /* already changed */ + continue; + } + + if ((*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) { + /* route does not get solo propagated to it */ + continue; + } + + if ((group_already_accounted_for && (*i)->route_group() && (*i)->route_group() == rg)) { + /* this route is a part of the same solo group as the route + * that was changed. Changing that route did change or will + * change all group members appropriately, so we can ignore it + * here + */ continue; } - (*i)->set_listen (false, this, group_override); + (*i)->set_listen (false, Controllable::NoGroup); } } @@ -3579,7 +3740,7 @@ Session::route_listen_changed (bool group_override, boost::weak_ptr wpr) update_route_solo_state (); } void -Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr wpr) +Session::route_solo_isolated_changed (boost::weak_ptr wpr) { boost::shared_ptr route = wpr.lock (); @@ -3609,7 +3770,7 @@ Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr wpr) } void -Session::route_solo_changed (bool self_solo_change, bool group_override, boost::weak_ptr wpr) +Session::route_solo_changed (bool self_solo_change, Controllable::GroupControlDisposition group_override, boost::weak_ptr wpr) { DEBUG_TRACE (DEBUG::Solo, string_compose ("route solo change, self = %1\n", self_solo_change)); @@ -3630,21 +3791,51 @@ Session::route_solo_changed (bool self_solo_change, bool group_override, boost: delta = -1; } + /* the route may be a member of a group that has shared-solo + * semantics. If so, then all members of that group should follow the + * solo of the changed route. But ... this is optional, controlled by a + * Controllable::GroupControlDisposition. + * + * The first argument to the signal that this method is connected to is the + * GroupControlDisposition value that was used to change solo. + * + * If the solo change was done with group semantics (either InverseGroup + * (force the entire group to change even if the group shared solo is + * disabled) or UseGroup (use the group, which may or may not have the + * shared solo property enabled)) then as we propagate the change to + * the entire session we should IGNORE THE GROUP that the changed route + * belongs to. + */ + RouteGroup* rg = route->route_group (); - bool leave_group_alone = (rg && rg->is_active() && rg->is_solo()); - if (group_override && rg) { - leave_group_alone = !leave_group_alone; - } + const bool group_already_accounted_for = (group_override == Controllable::ForGroup); + if (delta == 1 && Config->get_exclusive_solo()) { /* 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)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner() || - (leave_group_alone && ((*i)->route_group() == rg))) { + + if ((*i) == route) { + /* already changed */ + continue; + } + + if ((*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) { + /* route does not get solo propagated to it */ + continue; + } + + if ((group_already_accounted_for && (*i)->route_group() && (*i)->route_group() == rg)) { + /* this route is a part of the same solo group as the route + * that was changed. Changing that route did change or will + * change all group members appropriately, so we can ignore it + * here + */ continue; } - (*i)->set_solo (false, this, group_override); + + (*i)->set_solo (false, group_override); } } @@ -3658,8 +3849,22 @@ Session::route_solo_changed (bool self_solo_change, bool group_override, boost: bool via_sends_only; bool in_signal_flow; - if ((*i) == route || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner() || - (leave_group_alone && ((*i)->route_group() == rg))) { + if ((*i) == route) { + /* already changed */ + continue; + } + + if ((*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) { + /* route does not get solo propagated to it */ + continue; + } + + if ((group_already_accounted_for && (*i)->route_group() && (*i)->route_group() == rg)) { + /* this route is a part of the same solo group as the route + * that was changed. Changing that route did change or will + * change all group members appropriately, so we can ignore it + * here + */ continue; } @@ -3725,7 +3930,7 @@ Session::route_solo_changed (bool self_solo_change, bool group_override, boost: for (RouteList::iterator i = uninvolved.begin(); i != uninvolved.end(); ++i) { DEBUG_TRACE (DEBUG::Solo, string_compose ("mute change for %1, which neither feeds or is fed by %2\n", (*i)->name(), route->name())); (*i)->act_on_mute (); - (*i)->mute_changed (this); + (*i)->mute_changed (); } SoloChanged (); /* EMIT SIGNAL */ @@ -3738,6 +3943,7 @@ Session::update_route_solo_state (boost::shared_ptr r) /* now figure out if anything that matters is soloed (or is "listening")*/ bool something_soloed = false; + bool something_listening = false; uint32_t listeners = 0; uint32_t isolated = 0; @@ -3753,8 +3959,9 @@ Session::update_route_solo_state (boost::shared_ptr r) if (!(*i)->is_auditioner() && (*i)->listening_via_monitor()) { if (Config->get_solo_control_is_listen_control()) { listeners++; + something_listening = true; } else { - (*i)->set_listen (false, this); + (*i)->set_listen (false, Controllable::NoGroup); } } @@ -3768,6 +3975,11 @@ Session::update_route_solo_state (boost::shared_ptr r) SoloActive (_non_soloed_outs_muted); /* EMIT SIGNAL */ } + if (something_listening != _listening) { + _listening = something_listening; + SoloActive (_listening); + } + _listen_cnt = listeners; if (isolated != _solo_isolated_cnt) { @@ -3930,6 +4142,21 @@ Session::route_by_id (PBD::ID id) return boost::shared_ptr ((Route*) 0); } +boost::shared_ptr +Session::processor_by_id (PBD::ID id) const +{ + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + boost::shared_ptr p = (*i)->Route::processor_by_id (id); + if (p) { + return p; + } + } + + return boost::shared_ptr (); +} + boost::shared_ptr Session::track_by_diskstream_id (PBD::ID id) { @@ -3960,6 +4187,19 @@ Session::route_by_remote_id (uint32_t id) } +boost::shared_ptr +Session::route_by_selected_count (uint32_t id) +{ + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + /* NOT IMPLEMENTED */ + } + + return boost::shared_ptr ((Route*) 0); +} + + void Session::reassign_track_numbers () { @@ -4782,6 +5022,228 @@ Session::audition_playlist () queue_event (ev); } + +void +Session::register_lua_function ( + const std::string& name, + const std::string& script, + const LuaScriptParamList& args + ) +{ + Glib::Threads::Mutex::Lock lm (lua_lock); + + lua_State* L = lua.getState(); + + const std::string& bytecode = LuaScripting::get_factory_bytecode (script); + luabridge::LuaRef tbl_arg (luabridge::newTable(L)); + for (LuaScriptParamList::const_iterator i = args.begin(); i != args.end(); ++i) { + if ((*i)->optional && !(*i)->is_set) { continue; } + tbl_arg[(*i)->name] = (*i)->value; + } + (*_lua_add)(name, bytecode, tbl_arg); // throws luabridge::LuaException + set_dirty(); +} + +void +Session::unregister_lua_function (const std::string& name) +{ + Glib::Threads::Mutex::Lock lm (lua_lock); + (*_lua_del)(name); // throws luabridge::LuaException + lua.collect_garbage (); + set_dirty(); +} + +std::vector +Session::registered_lua_functions () +{ + Glib::Threads::Mutex::Lock lm (lua_lock); + std::vector rv; + + try { + luabridge::LuaRef list ((*_lua_list)()); + for (luabridge::Iterator i (list); !i.isNil (); ++i) { + if (!i.key ().isString ()) { assert(0); continue; } + rv.push_back (i.key ().cast ()); + } + } catch (luabridge::LuaException const& e) { } + return rv; +} + +#ifndef NDEBUG +static void _lua_print (std::string s) { + std::cout << "SessionLua: " << s << "\n"; +} +#endif + +void +Session::try_run_lua (pframes_t nframes) +{ + if (_n_lua_scripts == 0) return; + Glib::Threads::Mutex::Lock tm (lua_lock, Glib::Threads::TRY_LOCK); + if (tm.locked ()) { + try { (*_lua_run)(nframes); } catch (luabridge::LuaException const& e) { } + } +} + +void +Session::setup_lua () +{ +#ifndef NDEBUG + lua.Print.connect (&_lua_print); +#endif + lua.do_command ( + "function ArdourSession ()" + " local self = { scripts = {}, instances = {} }" + "" + " local remove = function (n)" + " self.scripts[n] = nil" + " self.instances[n] = nil" + " Session:scripts_changed()" // call back + " end" + "" + " local addinternal = function (n, f, a)" + " assert(type(n) == 'string', 'function-name must be string')" + " assert(type(f) == 'function', 'Given script is a not a function')" + " assert(type(a) == 'table' or type(a) == 'nil', 'Given argument is invalid')" + " assert(self.scripts[n] == nil, 'Callback \"'.. n ..'\" already exists.')" + " self.scripts[n] = { ['f'] = f, ['a'] = a }" + " local env = _ENV; env.f = nil env.io = nil env.os = nil env.loadfile = nil env.require = nil env.dofile = nil env.package = nil env.debug = nil" + " local env = { print = print, tostring = tostring, assert = assert, ipairs = ipairs, error = error, select = select, string = string, type = type, tonumber = tonumber, collectgarbage = collectgarbage, pairs = pairs, math = math, table = table, pcall = pcall, Session = Session, PBD = PBD, Timecode = Timecode, Evoral = Evoral, C = C, ARDOUR = ARDOUR }" + " self.instances[n] = load (string.dump(f, true), nil, nil, env)(a)" + " Session:scripts_changed()" // call back + " end" + "" + " local add = function (n, b, a)" + " assert(type(b) == 'string', 'ByteCode must be string')" + " load (b)()" // assigns f + " assert(type(f) == 'string', 'Assigned ByteCode must be string')" + " addinternal (n, load(f), a)" + " end" + "" + " local run = function (...)" + " for n, s in pairs (self.instances) do" + " local status, err = pcall (s, ...)" + " if not status then" + " print ('fn \"'.. n .. '\": ', err)" + " remove (n)" + " end" + " end" + " collectgarbage()" + " end" + "" + " local cleanup = function ()" + " self.scripts = nil" + " self.instances = nil" + " end" + "" + " local list = function ()" + " local rv = {}" + " for n, _ in pairs (self.scripts) do" + " rv[n] = true" + " end" + " return rv" + " end" + "" + " local function basic_serialize (o)" + " if type(o) == \"number\" then" + " return tostring(o)" + " else" + " return string.format(\"%q\", o)" + " end" + " end" + "" + " local function serialize (name, value)" + " local rv = name .. ' = '" + " collectgarbage()" + " if type(value) == \"number\" or type(value) == \"string\" or type(value) == \"nil\" then" + " return rv .. basic_serialize(value) .. ' '" + " elseif type(value) == \"table\" then" + " rv = rv .. '{} '" + " for k,v in pairs(value) do" + " local fieldname = string.format(\"%s[%s]\", name, basic_serialize(k))" + " rv = rv .. serialize(fieldname, v) .. ' '" + " collectgarbage()" // string concatenation allocates a new string :( + " end" + " return rv;" + " elseif type(value) == \"function\" then" + " return rv .. string.format(\"%q\", string.dump(value, true))" + " else" + " error('cannot save a ' .. type(value))" + " end" + " end" + "" + "" + " local save = function ()" + " return (serialize('scripts', self.scripts))" + " end" + "" + " local restore = function (state)" + " self.scripts = {}" + " load (state)()" + " for n, s in pairs (scripts) do" + " addinternal (n, load(s['f']), s['a'])" + " end" + " end" + "" + " return { run = run, add = add, remove = remove," + " list = list, restore = restore, save = save, cleanup = cleanup}" + " end" + " " + " sess = ArdourSession ()" + " ArdourSession = nil" + " " + "function ardour () end" + ); + + lua_State* L = lua.getState(); + + try { + luabridge::LuaRef lua_sess = luabridge::getGlobal (L, "sess"); + lua.do_command ("sess = nil"); // hide it. + lua.do_command ("collectgarbage()"); + + _lua_run = new luabridge::LuaRef(lua_sess["run"]); + _lua_add = new luabridge::LuaRef(lua_sess["add"]); + _lua_del = new luabridge::LuaRef(lua_sess["remove"]); + _lua_list = new luabridge::LuaRef(lua_sess["list"]); + _lua_save = new luabridge::LuaRef(lua_sess["save"]); + _lua_load = new luabridge::LuaRef(lua_sess["restore"]); + _lua_cleanup = new luabridge::LuaRef(lua_sess["cleanup"]); + } catch (luabridge::LuaException const& e) { + fatal << string_compose (_("programming error: %1"), + X_("Failed to setup Lua interpreter")) + << endmsg; + abort(); /*NOTREACHED*/ + } + + LuaBindings::stddef (L); + LuaBindings::common (L); + LuaBindings::dsp (L); + luabridge::push (L, this); + lua_setglobal (L, "Session"); +} + +void +Session::scripts_changed () +{ + assert (!lua_lock.trylock()); // must hold lua_lock + + try { + luabridge::LuaRef list ((*_lua_list)()); + int cnt = 0; + for (luabridge::Iterator i (list); !i.isNil (); ++i) { + if (!i.key ().isString ()) { assert(0); continue; } + ++cnt; + } + _n_lua_scripts = cnt; + } catch (luabridge::LuaException const& e) { + fatal << string_compose (_("programming error: %1"), + X_("Indexing Lua Session Scripts failed.")) + << endmsg; + abort(); /*NOTREACHED*/ + } +} + void Session::non_realtime_set_audition () { @@ -5006,7 +5468,7 @@ Session::next_insert_id () /* this doesn't really loop forever. just think about it */ while (true) { - for (boost::dynamic_bitset::size_type n = 0; n < insert_bitset.size(); ++n) { + for (boost::dynamic_bitset::size_type n = 1; n < insert_bitset.size(); ++n) { if (!insert_bitset[n]) { insert_bitset[n] = true; return n; @@ -5026,7 +5488,7 @@ Session::next_send_id () /* this doesn't really loop forever. just think about it */ while (true) { - for (boost::dynamic_bitset::size_type n = 0; n < send_bitset.size(); ++n) { + for (boost::dynamic_bitset::size_type n = 1; n < send_bitset.size(); ++n) { if (!send_bitset[n]) { send_bitset[n] = true; return n; @@ -5046,7 +5508,7 @@ 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) { + for (boost::dynamic_bitset::size_type n = 1; n < aux_send_bitset.size(); ++n) { if (!aux_send_bitset[n]) { aux_send_bitset[n] = true; return n; @@ -5066,7 +5528,7 @@ Session::next_return_id () /* this doesn't really loop forever. just think about it */ while (true) { - for (boost::dynamic_bitset::size_type n = 0; n < return_bitset.size(); ++n) { + for (boost::dynamic_bitset::size_type n = 1; n < return_bitset.size(); ++n) { if (!return_bitset[n]) { return_bitset[n] = true; return n; @@ -5147,6 +5609,7 @@ Session::unmark_aux_send_id (uint32_t id) void Session::unmark_return_id (uint32_t id) { + if (_state_of_the_state & Deletion) { return; } if (id < return_bitset.size()) { return_bitset[id] = false; } @@ -5496,6 +5959,12 @@ Session::get_scratch_buffers (ChanCount count, bool silence) return ProcessThread::get_scratch_buffers (count, silence); } +BufferSet& +Session::get_noinplace_buffers (ChanCount count) +{ + return ProcessThread::get_noinplace_buffers (count); +} + BufferSet& Session::get_route_buffers (ChanCount count, bool silence) { @@ -5933,9 +6402,10 @@ Session::unknown_processors () const void Session::update_latency (bool playback) { + DEBUG_TRACE (DEBUG::Latency, string_compose ("JACK latency callback: %1\n", (playback ? "PLAYBACK" : "CAPTURE"))); - if ((_state_of_the_state & (InitialConnecting|Deletion)) || _adding_routes_in_progress) { + if ((_state_of_the_state & (InitialConnecting|Deletion)) || _adding_routes_in_progress || _route_deletion_in_progress) { return; } @@ -6296,3 +6766,191 @@ Session::clear_object_selection () follow_playhead_priority (); #endif } + +void +Session::auto_connect_route (boost::shared_ptr route, bool connect_inputs, + const ChanCount& input_start, + const ChanCount& output_start, + const ChanCount& input_offset, + const ChanCount& output_offset) +{ + Glib::Threads::Mutex::Lock lx (_auto_connect_queue_lock); + _auto_connect_queue.push (AutoConnectRequest (route, connect_inputs, + input_start, output_start, + input_offset, output_offset)); + + if (pthread_mutex_trylock (&_auto_connect_mutex) == 0) { + pthread_cond_signal (&_auto_connect_cond); + pthread_mutex_unlock (&_auto_connect_mutex); + } +} + +void +Session::auto_connect (const AutoConnectRequest& ar) +{ + boost::shared_ptr route = ar.route.lock(); + + if (!route) { return; } + + if (!IO::connecting_legal) { + return; + } + + /* 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 + * first audio output of the route will have the same physical + * port number). Otherwise just use the lowest input or output + * offset possible. + */ + + const bool in_out_physical = + (Config->get_input_auto_connect() & AutoConnectPhysical) + && (Config->get_output_auto_connect() & AutoConnectPhysical) + && ar.connect_inputs; + + const ChanCount in_offset = in_out_physical + ? ChanCount::max(ar.input_offset, ar.output_offset) + : ar.input_offset; + + const ChanCount out_offset = in_out_physical + ? ChanCount::max(ar.input_offset, ar.output_offset) + : ar.output_offset; + + for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) { + vector physinputs; + vector physoutputs; + + _engine.get_physical_outputs (*t, physoutputs); + _engine.get_physical_inputs (*t, physinputs); + + if (!physinputs.empty() && ar.connect_inputs) { + uint32_t nphysical_in = physinputs.size(); + + for (uint32_t i = ar.input_start.get(*t); i < route->n_inputs().get(*t) && i < nphysical_in; ++i) { + string port; + + if (Config->get_input_auto_connect() & AutoConnectPhysical) { + port = physinputs[(in_offset.get(*t) + i) % nphysical_in]; + } + + if (!port.empty() && route->input()->connect (route->input()->ports().port(*t, i), port, this)) { + break; + } + } + } + + if (!physoutputs.empty()) { + uint32_t nphysical_out = physoutputs.size(); + for (uint32_t i = ar.output_start.get(*t); i < route->n_outputs().get(*t); ++i) { + string port; + + /* Waves Tracks: + * do not create new connections if we reached the limit of physical outputs + * in Multi Out mode + */ + if (!(Config->get_output_auto_connect() & AutoConnectMaster) && + ARDOUR::Profile->get_trx () && + ar.output_offset.get(*t) == nphysical_out ) { + break; + } + + if ((*t) == DataType::MIDI && (Config->get_output_auto_connect() & AutoConnectPhysical)) { + port = physoutputs[(out_offset.get(*t) + i) % nphysical_out]; + } else if ((*t) == DataType::AUDIO && (Config->get_output_auto_connect() & AutoConnectMaster)) { + /* 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)) { + break; + } + } + } + } +} + +void +Session::auto_connect_thread_start () +{ + if (_ac_thread_active) { + return; + } + + while (!_auto_connect_queue.empty ()) { + _auto_connect_queue.pop (); + } + + _ac_thread_active = true; + if (pthread_create (&_auto_connect_thread, NULL, auto_connect_thread, this)) { + _ac_thread_active = false; + } +} + +void +Session::auto_connect_thread_terminate () +{ + if (!_ac_thread_active) { + return; + } + _ac_thread_active = false; + + { + Glib::Threads::Mutex::Lock lx (_auto_connect_queue_lock); + while (!_auto_connect_queue.empty ()) { + _auto_connect_queue.pop (); + } + } + + if (pthread_mutex_lock (&_auto_connect_mutex) == 0) { + pthread_cond_signal (&_auto_connect_cond); + pthread_mutex_unlock (&_auto_connect_mutex); + } + + void *status; + pthread_join (_auto_connect_thread, &status); +} + +void * +Session::auto_connect_thread (void *arg) +{ + Session *s = static_cast(arg); + s->auto_connect_thread_run (); + pthread_exit (0); + return 0; +} + +void +Session::auto_connect_thread_run () +{ + pthread_set_name (X_("autoconnect")); + SessionEvent::create_per_thread_pool (X_("autoconnect"), 1024); + PBD::notify_event_loops_about_thread_creation (pthread_self(), X_("autoconnect"), 1024); + pthread_mutex_lock (&_auto_connect_mutex); + while (_ac_thread_active) { + + if (!_auto_connect_queue.empty ()) { + // Why would we need the process lock ?? + // A: if ports are added while we're connecting, the backend's iterator may be invalidated: + // graph_order_callback() -> resort_routes() -> direct_feeds_according_to_reality () -> backend::connected_to() + // All ardour-internal backends use a std::vector xxxAudioBackend::find_port() + // We have control over those, but what does jack do? + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + + Glib::Threads::Mutex::Lock lx (_auto_connect_queue_lock); + while (!_auto_connect_queue.empty ()) { + const AutoConnectRequest ar (_auto_connect_queue.front()); + _auto_connect_queue.pop (); + lx.release (); + auto_connect (ar); + lx.acquire (); + } + } + + pthread_cond_wait (&_auto_connect_cond, &_auto_connect_mutex); + } + pthread_mutex_unlock (&_auto_connect_mutex); +}