X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession.cc;h=8814d019780528f03d681eee0211f5cf4dfec77b;hb=6654c5376078c3ab5acfbf4e573fb11ffc9f44be;hp=b9d78494a3fefdd38d2365d3576d8d6d8ed0b521;hpb=3d1eb9a6e5250bfc2f4d0138c004a82d6186beab;p=ardour.git diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index b9d78494a3..8814d01978 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -94,9 +94,11 @@ #include "ardour/route_graph.h" #include "ardour/route_group.h" #include "ardour/send.h" +#include "ardour/selection.h" #include "ardour/session.h" #include "ardour/session_directory.h" #include "ardour/session_playlists.h" +#include "ardour/slave.h" #include "ardour/smf_source.h" #include "ardour/solo_isolate_control.h" #include "ardour/source_factory.h" @@ -104,6 +106,7 @@ #include "ardour/tempo.h" #include "ardour/ticker.h" #include "ardour/track.h" +#include "ardour/types_convert.h" #include "ardour/user_bundle.h" #include "ardour/utils.h" #include "ardour/vca_manager.h" @@ -184,6 +187,7 @@ Session::Session (AudioEngine &eng, , _transport_speed (0) , _default_transport_speed (1.0) , _last_transport_speed (0) + , _signalled_varispeed (0) , _target_transport_speed (0.0) , auto_play_legal (false) , _last_slave_transport_frame (0) @@ -222,7 +226,6 @@ Session::Session (AudioEngine &eng, , _is_new (true) , _send_qf_mtc (false) , _pframes_since_last_mtc (0) - , session_midi_feedback (0) , play_loop (false) , loop_changing (false) , last_loopend (0) @@ -241,7 +244,7 @@ Session::Session (AudioEngine &eng, , pending_locate_flush (false) , pending_abort (false) , pending_auto_loop (false) - , _mempool ("Session", 2097152) + , _mempool ("Session", 3145728) , lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool)) , _n_lua_scripts (0) , _butler (new Butler (*this)) @@ -252,7 +255,7 @@ Session::Session (AudioEngine &eng, , _ignore_skips_updates (false) , _rt_thread_active (false) , _rt_emit_pending (false) - , _ac_thread_active (false) + , _ac_thread_active (0) , _latency_recompute_pending (0) , step_speed (0) , outbound_mtc_timecode_frame (0) @@ -296,14 +299,19 @@ Session::Session (AudioEngine &eng, , _bundle_xml_node (0) , _current_trans (0) , _clicking (false) + , _click_rec_only (false) , click_data (0) , click_emphasis_data (0) , click_length (0) , click_emphasis_length (0) , _clicks_cleared (0) + , _count_in_samples (0) , _play_range (false) , _range_selection (-1,-1) , _object_selection (-1,-1) + , _preroll_record_punch_pos (-1) + , _preroll_record_trim_len (0) + , _count_in_once (false) , main_outs (0) , first_file_data_format_reset (true) , first_file_header_format_reset (true) @@ -319,6 +327,7 @@ Session::Session (AudioEngine &eng, , _midi_ports (0) , _mmc (0) , _vca_manager (new VCAManager (*this)) + , _selection (new CoreSelection (*this)) { uint32_t sr = 0; @@ -333,7 +342,7 @@ Session::Session (AudioEngine &eng, init_name_id_counter (1); // reset for new sessions, start at 1 VCA::set_next_vca_number (1); // reset for new sessions, start at 1 - pre_engine_init (fullpath); + pre_engine_init (fullpath); // sets _is_new setup_lua (); @@ -371,8 +380,12 @@ Session::Session (AudioEngine &eng, */ if (!mix_template.empty()) { - if (load_state (_current_snapshot_name)) { - throw SessionException (_("Failed to load template/snapshot state")); + try { + if (load_state (_current_snapshot_name)) { + throw SessionException (_("Failed to load template/snapshot state")); + } + } catch (PBD::unknown_enumeration& e) { + throw SessionException (_("Failed to parse template/snapshot state")); } store_recent_templates (mix_template); } @@ -405,9 +418,27 @@ Session::Session (AudioEngine &eng, } } - if (post_engine_init ()) { + int err = post_engine_init (); + if (err) { destroy (); - throw SessionException (_("Cannot configure audio/midi engine with session parameters")); + switch (err) { + case -1: + throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Failed to create background threads."))); + break; + case -2: + case -3: + throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Invalid TempoMap in session-file."))); + break; + case -4: + throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Invalid or corrupt session state."))); + break; + case -5: + throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Port registration failed."))); + break; + default: + throw SessionException (string_compose (_("Cannot initialize session/engine: %1"), _("Unexpected exception during session setup, possibly invalid audio/midi engine parameters. Please see stdout/stderr for details"))); + break; + } } store_recent_sessions (_name, _path); @@ -416,6 +447,8 @@ Session::Session (AudioEngine &eng, _state_of_the_state = StateOfTheState (_state_of_the_state & ~Dirty); + PresentationInfo::Change.connect_same_thread (*this, boost::bind (&Session::notify_presentation_info_change, this)); + Config->ParameterChanged.connect_same_thread (*this, boost::bind (&Session::config_changed, this, _1, false)); config.ParameterChanged.connect_same_thread (*this, boost::bind (&Session::config_changed, this, _1, true)); @@ -479,8 +512,18 @@ Session::Session (AudioEngine &eng, } #endif - _is_new = false; + ensure_subdirs (); // archived or zipped sessions may lack peaks/ analysis/ etc + + if (!mix_template.empty ()) { + /* ::create() unsets _is_new after creating the session. + * But for templated sessions, the sample-rate is initially unset + * (not read from template), so we need to save it (again). + */ + _is_new = true; + } + session_loaded (); + _is_new = false; BootMessage (_("Session loading complete")); } @@ -572,7 +615,6 @@ Session::immediately_post_engine () } try { - LocaleGuard lg; BootMessage (_("Set up LTC")); setup_ltc (); BootMessage (_("Set up Click")); @@ -607,18 +649,30 @@ Session::destroy () _state_of_the_state = StateOfTheState (CannotSave|Deletion); + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + ltc_tx_cleanup(); + delete _slave; + _slave = 0; + } + /* 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 - and the engine to move data to any devices. - */ + * and the engine to move data to any devices. + */ + + /* remove I/O objects before unsetting the engine session */ + _click_io.reset (); + _ltc_input.reset (); + _ltc_output.reset (); ControlProtocolManager::instance().drop_protocols (); - /* stop autoconnecting */ + /* stop auto dis/connecting */ auto_connect_thread_terminate (); MIDI::Name::MidiPatchManager::instance().remove_search_path(session_directory().midi_patch_path()); @@ -635,8 +689,6 @@ Session::destroy () Port::PortDrop (); /* EMIT SIGNAL */ - ltc_tx_cleanup(); - /* clear history so that no references to objects are held any more */ _history.clear (); @@ -646,17 +698,20 @@ Session::destroy () delete state_tree; state_tree = 0; - // 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 (); + { + /* unregister all lua functions, drop held references (if any) */ + Glib::Threads::Mutex::Lock tm (lua_lock, Glib::Threads::TRY_LOCK); + (*_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; @@ -779,6 +834,7 @@ Session::destroy () case SessionEvent::Skip: case SessionEvent::PunchIn: case SessionEvent::PunchOut: + case SessionEvent::RecordStart: case SessionEvent::StopOnce: case SessionEvent::RangeStop: case SessionEvent::RangeLocate: @@ -799,6 +855,18 @@ Session::destroy () } } + { + /* unregister all dropped ports, process pending port deletion. */ + // this may call ARDOUR::Port::drop ... jack_port_unregister () + // jack1 cannot cope with removing ports while processing + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + AudioEngine::instance()->clear_pending_port_deletions (); + } + + DEBUG_TRACE (DEBUG::Destruction, "delete selection\n"); + delete _selection; + _selection = 0; + DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n"); BOOST_SHOW_POINTERS (); @@ -1431,6 +1499,33 @@ Session::reset_monitor_section () } } +int +Session::add_master_bus (ChanCount const& count) +{ + if (master_out ()) { + return -1; + } + + RouteList rl; + + boost::shared_ptr r (new Route (*this, _("Master"), PresentationInfo::MasterOut, DataType::AUDIO)); + if (r->init ()) { + return -1; + } + + BOOST_MARK_ROUTE(r); + + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + r->input()->ensure_io (count, false, this); + r->output()->ensure_io (count, false, this); + } + + rl.push_back (r); + add_routes (rl, false, false, false, PresentationInfo::max_order); + return 0; +} + void Session::hookup_io () { @@ -1680,7 +1775,7 @@ Session::set_session_extents (framepos_t start, framepos_t end) Location* existing; if ((existing = _locations->session_range_location()) == 0) { //if there is no existing session, we need to make a new session location (should never happen) - existing = new Location (*this, 0, 0, _("session"), Location::IsSessionRange); + existing = new Location (*this, 0, 0, _("session"), Location::IsSessionRange, 0); } if (end <= start) { @@ -1869,6 +1964,17 @@ Session::location_added (Location *location) location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); + location->PositionLockStyleChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); + } + + if (location->is_range_marker()) { + /* listen for per-location signals that require us to do any * global updates for marks */ + + location->StartChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); + location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); + location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); + location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); + location->PositionLockStyleChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); } if (location->is_skip()) { @@ -1878,6 +1984,7 @@ Session::location_added (Location *location) location->EndChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true)); location->Changed.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, true)); location->FlagsChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_skips, this, location, false)); + location->PositionLockStyleChanged.connect_same_thread (skip_update_connections, boost::bind (&Session::update_marks, this, location)); update_skips (location, true); } @@ -1997,6 +2104,7 @@ Session::disable_record (bool rt_context, bool force) if (!rt_context) { remove_pending_capture_state (); } + unset_preroll_record_punch (); } } @@ -2014,7 +2122,7 @@ Session::step_back_from_record () } void -Session::maybe_enable_record () +Session::maybe_enable_record (bool rt_context) { if (_step_editors > 0) { return; @@ -2023,15 +2131,18 @@ Session::maybe_enable_record () g_atomic_int_set (&_record_status, Enabled); /* This function is currently called from somewhere other than an RT thread. - This save_state() call therefore doesn't impact anything. Doing it here - 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. - */ + * (except maybe lua scripts, which can use rt_context = true) + * This save_state() call therefore doesn't impact anything. Doing it here + * 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 (!rt_context) { + save_state ("", true); + } if (_transport_speed) { - if (!config.get_punch_in()) { + if (!config.get_punch_in() && !preroll_record_punch_enabled ()) { enable_record (); } } else { @@ -2043,12 +2154,15 @@ Session::maybe_enable_record () } framepos_t -Session::audible_frame () const +Session::audible_frame (bool* latent_locate) const { framepos_t ret; frameoffset_t offset = worst_playback_latency (); // - _engine.samples_since_cycle_start (); offset *= transport_speed (); + if (latent_locate) { + *latent_locate = false; + } if (synced_to_engine()) { /* Note: this is basically just sync-to-JACK */ @@ -2073,14 +2187,24 @@ Session::audible_frame () const if (!play_loop || !have_looped) { if (ret < _last_roll_or_reversal_location) { + if (latent_locate) { + *latent_locate = true; + } return _last_roll_or_reversal_location; } } else { - // latent loops + /* the play-position wrapped at the loop-point + * ardour is already playing the beginning of the loop, + * but due to playback latency, the "audible frame" + * is still at the end of the loop. + */ Location *location = _locations->auto_loop_location(); frameoffset_t lo = location->start() - ret; if (lo > 0) { ret = location->end () - lo; + if (latent_locate) { + *latent_locate = true; + } } } @@ -2097,6 +2221,22 @@ Session::audible_frame () const return std::max ((framepos_t)0, ret); } + +framecnt_t +Session::preroll_samples (framepos_t pos) const +{ + const float pr = Config->get_preroll_seconds(); + if (pos >= 0 && pr < 0) { + const Tempo& tempo = _tempo_map->tempo_at_frame (pos); + const Meter& meter = _tempo_map->meter_at_frame (pos); + return meter.frames_per_bar (tempo, frame_rate()) * -pr; + } + if (pr < 0) { + return 0; + } + return pr * frame_rate(); +} + void Session::set_frame_rate (framecnt_t frames_per_second) { @@ -2417,11 +2557,13 @@ Session::count_existing_track_channels (ChanCount& in, ChanCount& out) boost::shared_ptr r = routes.reader (); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); - if (tr && !tr->is_auditioner()) { - in += tr->n_inputs(); - out += tr->n_outputs(); + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (!tr) { + continue; } + assert (!tr->is_auditioner()); // XXX remove me + in += tr->n_inputs(); + out += tr->n_outputs(); } } @@ -2449,9 +2591,10 @@ Session::default_track_name_pattern (DataType t) * @param instrument plugin info for the instrument to insert pre-fader, if any */ list > -Session::new_midi_track (const ChanCount& input, const ChanCount& output, +Session::new_midi_track (const ChanCount& input, const ChanCount& output, bool strict_io, boost::shared_ptr instrument, Plugin::PresetRecord* pset, - RouteGroup* route_group, uint32_t how_many, string name_template, PresentationInfo::order_t order, + RouteGroup* route_group, uint32_t how_many, + string name_template, PresentationInfo::order_t order, TrackMode mode) { string track_name; @@ -2478,7 +2621,7 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, goto failed; } - if (Profile->get_mixbus ()) { + if (strict_io) { track->set_strict_io (true); } @@ -2544,8 +2687,16 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, if (pset) { plugin->load_preset (*pset); } - boost::shared_ptr p (new PluginInsert (*this, plugin)); - (*r)->add_processor (p, PreFader); + boost::shared_ptr pi (new PluginInsert (*this, plugin)); + if (strict_io) { + pi->set_strict_io (true); + } + + (*r)->add_processor (pi, PreFader); + + if (Profile->get_mixbus () && pi->configured () && pi->output_streams().n_audio() > 2) { + (*r)->move_instrument_down (false); + } } } } @@ -2554,7 +2705,8 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, } RouteList -Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name_template, boost::shared_ptr instrument, Plugin::PresetRecord* pset, +Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name_template, bool strict_io, + boost::shared_ptr instrument, Plugin::PresetRecord* pset, PresentationInfo::Flag flag, PresentationInfo::order_t order) { string bus_name; @@ -2577,7 +2729,7 @@ Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name goto failure; } - if (Profile->get_mixbus ()) { + if (strict_io) { bus->set_strict_io (true); } @@ -2635,8 +2787,16 @@ Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name if (pset) { plugin->load_preset (*pset); } - boost::shared_ptr p (new PluginInsert (*this, plugin)); - (*r)->add_processor (p, PreFader); + boost::shared_ptr pi (new PluginInsert (*this, plugin)); + if (strict_io) { + pi->set_strict_io (true); + } + + (*r)->add_processor (pi, PreFader); + + if (Profile->get_mixbus () && pi->configured () && pi->output_streams().n_audio() > 2) { + (*r)->move_instrument_down (false); + } } } } @@ -2954,9 +3114,36 @@ Session::reconnect_mmc_ports(bool inputs) #endif +bool +Session::ensure_stripable_sort_order () +{ + StripableList sl; + get_stripables (sl); + sl.sort (Stripable::Sorter ()); + + bool change = false; + PresentationInfo::order_t order = 0; + + for (StripableList::iterator si = sl.begin(); si != sl.end(); ++si) { + boost::shared_ptr s (*si); + assert (!s->is_auditioner ()); // XXX remove me + if (s->is_monitor ()) { + continue; + } + if (order != s->presentation_info().order()) { + s->set_presentation_order (order); + change = true; + } + ++order; + } + return change; +} + void Session::ensure_route_presentation_info_gap (PresentationInfo::order_t first_new_order, uint32_t how_many) { + DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("ensure order gap starting at %1 for %2\n", first_new_order, how_many)); + if (first_new_order == PresentationInfo::max_order) { /* adding at end, no worries */ return; @@ -2971,7 +3158,11 @@ Session::ensure_route_presentation_info_gap (PresentationInfo::order_t first_new for (StripableList::iterator si = sl.begin(); si != sl.end(); ++si) { boost::shared_ptr s (*si); - if (s->is_monitor() || s->is_auditioner()) { + if (s->presentation_info().special (false)) { + continue; + } + + if (!s->presentation_info().order_set()) { continue; } @@ -3251,7 +3442,15 @@ Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t i boost::shared_ptr playlist = playlists->by_name (playlist_name); // Use same name as Route::set_name_in_state so playlist copy // is picked up when creating the Route in XMLRouteFactory below - PlaylistFactory::create (playlist, string_compose ("%1.1", name)); + playlist = PlaylistFactory::create (playlist, string_compose ("%1.1", name)); + playlist->reset_shares (); + } + } else if (pd == SharePlaylist) { + XMLNode* ds_node = find_named_node (node_copy, "Diskstream"); + if (ds_node) { + const std::string playlist_name = ds_node->property (X_("playlist"))->value (); + boost::shared_ptr playlist = playlists->by_name (playlist_name); + playlist->share_with ((node_copy.property (X_("id")))->value()); } } @@ -3272,12 +3471,12 @@ Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t i */ XMLProperty const * target = (*i)->property (X_("target")); if (!target) { - (*i)->add_property ("type", "dangling-aux-send"); + (*i)->set_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"); + (*i)->set_property ("type", "dangling-aux-send"); continue; } } @@ -3285,20 +3484,18 @@ Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t i (*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); + (*i)->set_property ("bitslot", bitslot); + (*i)->set_property ("name", name); } else if (type && type->value() == X_("intreturn")) { (*i)->remove_property (X_("bitslot")); - (*i)->add_property ("ignore-bitslot", "1"); + (*i)->set_property ("ignore-bitslot", "1"); } else if (type && type->value() == X_("return")) { // Return::set_state() generates a new one @@ -3307,11 +3504,15 @@ Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t i else if (type && type->value() == X_("port")) { // PortInsert::set_state() handles the bitslot (*i)->remove_property (X_("bitslot")); - (*i)->add_property ("ignore-name", "1"); + (*i)->set_property ("ignore-name", "1"); } } } + /* new routes start off unsoloed to avoid issues related to + upstream / downstream buses. */ + node_copy.remove_node_and_delete (X_("Controllable"), X_("name"), X_("solo")); + boost::shared_ptr route (XMLRouteFactory (node_copy, 3000)); if (route == 0) { @@ -3404,8 +3605,8 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool { RCUWriter writer (routes); boost::shared_ptr r = writer.get_copy (); - r->insert (r->end(), new_routes.begin(), new_routes.end()); n_routes = r->size(); + 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 @@ -3418,84 +3619,92 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool } } - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("ensure order gap starting at %1 for %2\n", order, new_routes.size())); - ensure_route_presentation_info_gap (order, new_routes.size()); + /* monitor is not part of the order */ + if (_monitor_out) { + assert (n_routes > 0); + --n_routes; + } - for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x, ++added) { + { + PresentationInfo::ChangeSuspender cs; + ensure_route_presentation_info_gap (order, new_routes.size()); - boost::weak_ptr wpr (*x); - boost::shared_ptr r (*x); + for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x, ++added) { - r->solo_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _2,wpr)); - r->solo_isolate_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, wpr)); - r->mute_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this)); + boost::weak_ptr wpr (*x); + boost::shared_ptr r (*x); - r->output()->changed.connect_same_thread (*this, boost::bind (&Session::set_worst_io_latencies_x, this, _1, _2)); - r->processors_changed.connect_same_thread (*this, boost::bind (&Session::route_processors_changed, this, _1)); - r->processor_latency_changed.connect_same_thread (*this, boost::bind (&Session::queue_latency_recompute, this)); + r->solo_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _2,wpr)); + r->solo_isolate_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, wpr)); + r->mute_control()->Changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this)); - if (r->is_master()) { - _master_out = r; - } + r->output()->changed.connect_same_thread (*this, boost::bind (&Session::set_worst_io_latencies_x, this, _1, _2)); + r->processors_changed.connect_same_thread (*this, boost::bind (&Session::route_processors_changed, this, _1)); + r->processor_latency_changed.connect_same_thread (*this, boost::bind (&Session::queue_latency_recompute, this)); - if (r->is_monitor()) { - _monitor_out = r; - } + if (r->is_master()) { + _master_out = r; + } - boost::shared_ptr tr = boost::dynamic_pointer_cast (r); - if (tr) { - tr->PlaylistChanged.connect_same_thread (*this, boost::bind (&Session::track_playlist_changed, this, boost::weak_ptr (tr))); - track_playlist_changed (boost::weak_ptr (tr)); - tr->rec_enable_control()->Changed.connect_same_thread (*this, boost::bind (&Session::update_route_record_state, this)); - - 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->presentation_info().PropertyChanged.connect_same_thread (*this, boost::bind (&Session::midi_track_presentation_info_changed, this, _1, boost::weak_ptr(mt))); + if (r->is_monitor()) { + _monitor_out = r; } - } - if (!r->presentation_info().special()) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (r); + if (tr) { + tr->PlaylistChanged.connect_same_thread (*this, boost::bind (&Session::track_playlist_changed, this, boost::weak_ptr (tr))); + track_playlist_changed (boost::weak_ptr (tr)); + tr->rec_enable_control()->Changed.connect_same_thread (*this, boost::bind (&Session::update_route_record_state, this)); + + 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->presentation_info().PropertyChanged.connect_same_thread (*this, boost::bind (&Session::midi_track_presentation_info_changed, this, _1, boost::weak_ptr(mt))); + } + } - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("checking PI state for %1\n", r->name())); + if (!r->presentation_info().special (false)) { - /* presentation info order may already have been set from XML */ + DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("checking PI state for %1\n", r->name())); - if (!r->presentation_info().order_set()) { + /* presentation info order may already have been set from XML */ - if (order == PresentationInfo::max_order) { - /* just add to the end */ - r->set_presentation_order (n_routes + added, false); - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to NR %1 + %2 = %3\n", n_routes, added, n_routes + added)); + if (!r->presentation_info().order_set()) { + if (order == PresentationInfo::max_order) { + /* just add to the end */ + r->set_presentation_order (n_routes + added); + DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to NR %1 + %2 = %3\n", n_routes, added, n_routes + added)); + } else { + r->set_presentation_order (order + added); + DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to %1 + %2 = %3\n", order, added, order + added)); + } } else { - r->set_presentation_order (order + added); - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order not set, set to %1 + %2 = %3\n", order, added, order + added)); + DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order already set to %1\n", r->presentation_info().order())); } - } else { - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("group order already set to %1\n", r->presentation_info().order())); } - } #if !defined(__APPLE__) && !defined(__FreeBSD__) - /* clang complains: 'operator<<' should be declared prior to the call site or in an associated namespace of one of its - * arguments std::ostream& operator<<(std::ostream& o, ARDOUR::PresentationInfo const& rid)" - */ - DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("added route %1, group order %2 type %3 (summary: %4)\n", - r->name(), - r->presentation_info().order(), - enum_2_string (r->presentation_info().flags()), - r->presentation_info())); + /* clang complains: 'operator<<' should be declared prior to the call site or in an associated namespace of one of its + * arguments std::ostream& operator<<(std::ostream& o, ARDOUR::PresentationInfo const& rid)" + */ + DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("added route %1, group order %2 type %3 (summary: %4)\n", + r->name(), + r->presentation_info().order(), + enum_2_string (r->presentation_info().flags()), + r->presentation_info())); #endif - if (input_auto_connect || output_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(); - } + if (input_auto_connect || output_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(); + } - ARDOUR::GUIIdle (); + ARDOUR::GUIIdle (); + } + ensure_stripable_sort_order (); } if (_monitor_out && IO::connecting_legal) { @@ -3604,14 +3813,20 @@ Session::add_internal_send (boost::shared_ptr dest, boost::shared_ptr routes_to_remove) { + bool mute_changed = false; + bool send_selected = false; + { // RCU Writer scope PBD::Unwinder uw_flag (_route_deletion_in_progress, true); RCUWriter writer (routes); boost::shared_ptr rs = writer.get_copy (); - for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) { + if (_selection->selected (*iter)) { + send_selected = true; + } + if (*iter == _master_out) { continue; } @@ -3621,6 +3836,10 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) (*iter)->solo_control()->set_value (0.0, Controllable::NoGroup); } + if ((*iter)->mute_control()->muted ()) { + mute_changed = true; + } + rs->remove (*iter); /* deleting the master out seems like a dumb @@ -3672,6 +3891,10 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) } // end of RCU Writer scope + if (mute_changed) { + MuteChanged (); /* EMIT SIGNAL */ + } + update_route_solo_state (); update_latency_compensation (); set_dirty(); @@ -3697,20 +3920,34 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) routes.flush (); + /* remove these routes from the selection if appropriate, and signal + * the change *before* we call DropReferences for them. + */ + + if (send_selected && !deletion_in_progress()) { + for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) { + _selection->remove_stripable_by_id ((*iter)->id()); + } + PropertyChange pc; + pc.add (Properties::selected); + PresentationInfo::Change (pc); + } + /* try to cause everyone to drop their references * and unregister ports from the backend */ for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) { - cerr << "Drop references to " << (*iter)->name() << endl; (*iter)->drop_references (); } - if (_state_of_the_state & Deletion) { + if (deletion_in_progress()) { return; } - PresentationInfo::Change(); /* EMIT SIGNAL */ + PropertyChange pc; + pc.add (Properties::order); + PresentationInfo::Change (pc); /* save the new state of the world */ @@ -3732,6 +3969,7 @@ Session::remove_route (boost::shared_ptr route) void Session::route_mute_changed () { + MuteChanged (); /* EMIT SIGNAL */ set_dirty (); } @@ -4049,6 +4287,61 @@ Session::update_route_solo_state (boost::shared_ptr r) set_dirty(); } +bool +Session::muted () const +{ + // TODO consider caching the value on every MuteChanged signal, + // Note that API users may also subscribe to MuteChanged and hence + // this method needs to be called first. + bool muted = false; + StripableList all; + get_stripables (all); + for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) { + assert (!(*i)->is_auditioner()); // XXX remove me + if ((*i)->is_monitor()) { + continue; + } + boost::shared_ptr r = boost::dynamic_pointer_cast(*i); + if (r && !r->active()) { + continue; + } + boost::shared_ptr mc = (*i)->mute_control(); + if (mc && mc->muted ()) { + muted = true; + break; + } + } + return muted; +} + +std::vector > +Session::cancel_all_mute () +{ + StripableList all; + get_stripables (all); + std::vector > muted; + boost::shared_ptr cl (new ControlList); + for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) { + assert (!(*i)->is_auditioner()); + if ((*i)->is_monitor()) { + continue; + } + boost::shared_ptr r = boost::dynamic_pointer_cast (*i); + if (r && !r->active()) { + continue; + } + boost::shared_ptr ac = (*i)->mute_control(); + if (ac && ac->get_value () > 0) { + cl->push_back (ac); + muted.push_back (boost::weak_ptr(ac)); + } + } + if (!cl->empty ()) { + set_controls (cl, 0.0, PBD::Controllable::UseGroup); + } + return muted; +} + void Session::get_stripables (StripableList& sl) const { @@ -4059,6 +4352,15 @@ Session::get_stripables (StripableList& sl) const sl.insert (sl.end(), v.begin(), v.end()); } +StripableList +Session::get_stripables () const +{ + StripableList rv; + Session::get_stripables (rv); + rv.sort (Stripable::Sorter ()); + return rv; +} + boost::shared_ptr Session::get_routes_with_internal_returns() const { @@ -4081,8 +4383,8 @@ Session::io_name_is_legal (const std::string& name) const for (map::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) { if (name == reserved->first) { if (!route_by_name (reserved->first)) { - /* first instance of a reserved name is allowed */ - return true; + /* first instance of a reserved name is allowed for some */ + return reserved->second; } /* all other instances of a reserved name are not allowed */ return false; @@ -4210,6 +4512,22 @@ Session::route_by_id (PBD::ID id) const return boost::shared_ptr ((Route*) 0); } + +boost::shared_ptr +Session::stripable_by_id (PBD::ID id) const +{ + StripableList sl; + get_stripables (sl); + + for (StripableList::const_iterator s = sl.begin(); s != sl.end(); ++s) { + if ((*s)->id() == id) { + return *s; + } + } + + return boost::shared_ptr(); +} + boost::shared_ptr Session::processor_by_id (PBD::ID id) const { @@ -4253,7 +4571,7 @@ Session::get_remote_nth_stripable (PresentationInfo::order_t n, PresentationInfo PresentationInfo::order_t match_cnt = 0; get_stripables (sl); - sl.sort (Stripable::PresentationOrderSorter()); + sl.sort (Stripable::Sorter()); for (StripableList::const_iterator s = sl.begin(); s != sl.end(); ++s) { @@ -4288,28 +4606,22 @@ Session::get_remote_nth_stripable (PresentationInfo::order_t n, PresentationInfo boost::shared_ptr Session::route_by_selected_count (uint32_t id) const { - boost::shared_ptr r = routes.reader (); - - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - /* NOT IMPLEMENTED */ - } + RouteList r (*(routes.reader ())); + r.sort (Stripable::Sorter()); - return boost::shared_ptr ((Route*) 0); -} + RouteList::iterator i; -struct PresentationOrderSorter { - bool operator() (boost::shared_ptr a, boost::shared_ptr b) { - if (a->presentation_info().special() && !b->presentation_info().special()) { - /* a is not ordered, b is; b comes before a */ - return false; - } else if (!b->presentation_info().order_set() && a->presentation_info().order_set()) { - /* b is not ordered, a is; a comes before b */ - return true; - } else { - return a->presentation_info().order() < b->presentation_info().order(); + for (i = r.begin(); i != r.end(); ++i) { + if ((*i)->is_selected()) { + if (id == 0) { + return *i; + } + --id; } } -}; + + return boost::shared_ptr (); +} void Session::reassign_track_numbers () @@ -4317,16 +4629,16 @@ Session::reassign_track_numbers () int64_t tn = 0; int64_t bn = 0; RouteList r (*(routes.reader ())); - PresentationOrderSorter sorter; - r.sort (sorter); + r.sort (Stripable::Sorter()); StateProtector sp (this); for (RouteList::iterator i = r.begin(); i != r.end(); ++i) { + assert (!(*i)->is_auditioner()); if (boost::dynamic_pointer_cast (*i)) { (*i)->set_track_number(++tn); } - else if (!(*i)->is_master() && !(*i)->is_monitor() && !(*i)->is_auditioner()) { + else if (!(*i)->is_master() && !(*i)->is_monitor()) { (*i)->set_track_number(--bn); } } @@ -4653,13 +4965,16 @@ Session::audio_source_by_path_and_channel (const string& path, uint16_t chn) con } boost::shared_ptr -Session::midi_source_by_path (const std::string& path) const +Session::midi_source_by_path (const std::string& path, bool need_source_lock) const { /* Restricted to MIDI files because audio sources require a channel for unique identification, in addition to a path. */ - Glib::Threads::Mutex::Lock lm (source_lock); + Glib::Threads::Mutex::Lock lm (source_lock, Glib::Threads::NOT_LOCK); + if (need_source_lock) { + lm.acquire (); + } for (SourceMap::const_iterator s = sources.begin(); s != sources.end(); ++s) { boost::shared_ptr ms @@ -4960,17 +5275,12 @@ Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t cha /** Return a unique name based on `base` for a new internal MIDI source */ string -Session::new_midi_source_path (const string& base) +Session::new_midi_source_path (const string& base, bool need_lock) { - uint32_t cnt; - char buf[PATH_MAX+1]; - const uint32_t limit = 10000; - string legalized; string possible_path; string possible_name; - buf[0] = '\0'; - legalized = legalize_for_path (base); + possible_name = legalize_for_path (base); // Find a "version" of the file name that doesn't exist in any of the possible directories. std::vector sdirs = source_search_path(DataType::MIDI); @@ -4985,38 +5295,36 @@ Session::new_midi_source_path (const string& base) */ std::reverse(sdirs.begin(), sdirs.end()); - for (cnt = 1; cnt <= limit; ++cnt) { + while (true) { + possible_name = bump_name_once (possible_name, '-'); vector::iterator i; uint32_t existing = 0; for (vector::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) { - snprintf (buf, sizeof(buf), "%s-%u.mid", legalized.c_str(), cnt); - possible_name = buf; - - possible_path = Glib::build_filename (*i, possible_name); + possible_path = Glib::build_filename (*i, possible_name + ".mid"); if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) { existing++; } - if (midi_source_by_path (possible_path)) { + if (midi_source_by_path (possible_path, need_lock)) { existing++; } } - if (existing == 0) { - break; - } - - if (cnt > limit) { + if (possible_path.size () >= PATH_MAX) { error << string_compose( - _("There are already %1 recordings for %2, which I consider too many."), - limit, base) << endmsg; + _("There are already many recordings for %1, resulting in a too long file-path %2."), + base, possible_path) << endmsg; destroy (); return 0; } + + if (existing == 0) { + break; + } } /* No need to "find best location" for software/app-based RAID, because @@ -5168,6 +5476,9 @@ Session::register_lua_function ( tbl_arg[(*i)->name] = (*i)->value; } (*_lua_add)(name, bytecode, tbl_arg); // throws luabridge::LuaException + lm.release(); + + LuaScriptsChanged (); /* EMIT SIGNAL */ set_dirty(); } @@ -5177,6 +5488,9 @@ Session::unregister_lua_function (const std::string& name) Glib::Threads::Mutex::Lock lm (lua_lock); (*_lua_del)(name); // throws luabridge::LuaException lua.collect_garbage (); + lm.release(); + + LuaScriptsChanged (); /* EMIT SIGNAL */ set_dirty(); } @@ -5192,7 +5506,7 @@ Session::registered_lua_functions () if (!i.key ().isString ()) { assert(0); continue; } rv.push_back (i.key ().cast ()); } - } catch (luabridge::LuaException const& e) { } + } catch (...) { } return rv; } @@ -5208,7 +5522,7 @@ 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) { } + try { (*_lua_run)(nframes); } catch (...) { } lua.collect_garbage_step (); } } @@ -5220,6 +5534,7 @@ Session::setup_lua () lua.Print.connect (&_lua_print); #endif lua.tweak_rt_gc (); + lua.sandbox (true); lua.do_command ( "function ArdourSession ()" " local self = { scripts = {}, instances = {} }" @@ -5236,8 +5551,7 @@ Session::setup_lua () " 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 }" + " 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, bit32=bit32, 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" @@ -5340,7 +5654,12 @@ Session::setup_lua () _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")) + std::string ("Failed to setup session Lua interpreter") + e.what ()) + << endmsg; + abort(); /*NOTREACHED*/ + } catch (...) { + fatal << string_compose (_("programming error: %1"), + X_("Failed to setup session Lua interpreter")) << endmsg; abort(); /*NOTREACHED*/ } @@ -5366,6 +5685,11 @@ Session::scripts_changed () } _n_lua_scripts = cnt; } catch (luabridge::LuaException const& e) { + fatal << string_compose (_("programming error: %1"), + std::string ("Indexing Lua Session Scripts failed.") + e.what ()) + << endmsg; + abort(); /*NOTREACHED*/ + } catch (...) { fatal << string_compose (_("programming error: %1"), X_("Indexing Lua Session Scripts failed.")) << endmsg; @@ -5402,18 +5726,6 @@ Session::cancel_audition () } } -bool -Session::RoutePublicOrderSorter::operator() (boost::shared_ptr a, boost::shared_ptr b) -{ - if (a->is_monitor()) { - return true; - } - if (b->is_monitor()) { - return false; - } - return a->presentation_info().order() < b->presentation_info().order(); -} - bool Session::is_auditioning () const { @@ -5566,21 +5878,11 @@ Session::tempo_map_changed (const PropertyChange&) set_dirty (); } -void -Session::gui_tempo_map_changed () -{ - clear_clicks (); - - playlists->update_after_tempo_map_change (); - - _locations->apply (*this, &Session::update_locations_after_tempo_map_change); -} - void Session::update_locations_after_tempo_map_change (const Locations::LocationList& loc) { for (Locations::LocationList::const_iterator i = loc.begin(); i != loc.end(); ++i) { - (*i)->recompute_frames_from_bbt (); + (*i)->recompute_frames_from_beat (); } } @@ -6080,6 +6382,12 @@ Session::send_gain_automation_buffer() const return ProcessThread::send_gain_automation_buffer (); } +gain_t* +Session::scratch_automation_buffer() const +{ + return ProcessThread::scratch_automation_buffer (); +} + pan_t** Session::pan_automation_buffer() const { @@ -6147,6 +6455,23 @@ Session::nbusses () const return n; } +uint32_t +Session::nstripables (bool with_monitor) const +{ + uint32_t rv = routes.reader()->size (); + rv += _vca_manager->vcas ().size (); + + if (with_monitor) { + return rv; + } + + if (_monitor_out) { + assert (rv > 0); + --rv; + } + return rv; +} + void Session::add_automation_list(AutomationList *al) { @@ -6270,9 +6595,8 @@ Session::get_tracks () const for (RouteList::const_iterator r = rl->begin(); r != rl->end(); ++r) { if (boost::dynamic_pointer_cast (*r)) { - if (!(*r)->is_auditioner()) { - tl->push_back (*r); - } + assert (!(*r)->is_auditioner()); // XXX remove me + tl->push_back (*r); } } return tl; @@ -6338,7 +6662,7 @@ Session::current_end_frame () const void Session::set_session_range_location (framepos_t start, framepos_t end) { - _session_range_location = new Location (*this, start, end, _("session"), Location::IsSessionRange); + _session_range_location = new Location (*this, start, end, _("session"), Location::IsSessionRange, 0); _locations->add (_session_range_location); } @@ -6560,6 +6884,9 @@ Session::update_latency (bool playback) if ((_state_of_the_state & (InitialConnecting|Deletion)) || _adding_routes_in_progress || _route_deletion_in_progress) { return; } + if (!_engine.running()) { + return; + } boost::shared_ptr r = routes.reader (); framecnt_t max_latency = 0; @@ -6613,7 +6940,8 @@ Session::post_playback_latency () boost::shared_ptr r = routes.reader (); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - if (!(*i)->is_auditioner() && ((*i)->active())) { + assert (!(*i)->is_auditioner()); // XXX remove me + if ((*i)->active()) { _worst_track_latency = max (_worst_track_latency, (*i)->update_signal_latency ()); } } @@ -6719,7 +7047,8 @@ Session::update_latency_compensation (bool force_whole_graph) boost::shared_ptr r = routes.reader (); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - if (!(*i)->is_auditioner() && ((*i)->active())) { + assert (!(*i)->is_auditioner()); // XXX remove me + if ((*i)->active()) { framecnt_t tl; if ((*i)->signal_latency () != (tl = (*i)->update_signal_latency ())) { some_track_latency_changed = true; @@ -6768,7 +7097,6 @@ Session::notify_presentation_info_change () return; } - PresentationInfo::Change (); /* EMIT SIGNAL */ reassign_track_numbers(); #ifdef USE_TRACKS_CODE_FEATURES @@ -6789,13 +7117,14 @@ Session::operation_in_progress (GQuark op) const boost::shared_ptr Session::ltc_input_port () const { + assert (_ltc_input); return _ltc_input->nth (0); } boost::shared_ptr Session::ltc_output_port () const { - return _ltc_output->nth (0); + return _ltc_output ? _ltc_output->nth (0) : boost::shared_ptr (); } void @@ -6995,7 +7324,7 @@ Session::auto_connect (const AutoConnectRequest& ar) void Session::auto_connect_thread_start () { - if (_ac_thread_active) { + if (g_atomic_int_get (&_ac_thread_active)) { return; } @@ -7003,19 +7332,18 @@ Session::auto_connect_thread_start () _auto_connect_queue.pop (); } - _ac_thread_active = true; + g_atomic_int_set (&_ac_thread_active, 1); if (pthread_create (&_auto_connect_thread, NULL, auto_connect_thread, this)) { - _ac_thread_active = false; + g_atomic_int_set (&_ac_thread_active, 0); } } void Session::auto_connect_thread_terminate () { - if (!_ac_thread_active) { + if (!g_atomic_int_get (&_ac_thread_active)) { return; } - _ac_thread_active = false; { Glib::Threads::Mutex::Lock lx (_auto_connect_queue_lock); @@ -7029,6 +7357,7 @@ Session::auto_connect_thread_terminate () */ pthread_mutex_lock (&_auto_connect_mutex); + g_atomic_int_set (&_ac_thread_active, 0); pthread_cond_signal (&_auto_connect_cond); pthread_mutex_unlock (&_auto_connect_mutex); @@ -7052,7 +7381,7 @@ Session::auto_connect_thread_run () 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) { + while (g_atomic_int_get (&_ac_thread_active)) { if (!_auto_connect_queue.empty ()) { // Why would we need the process lock ??