X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession.cc;h=b658487969c3827479096ab426e66c538c20892d;hb=cb0d9f5607da6465efa619cc7f5fb2152bf1b498;hp=65e810f48e50ba52cfa76f69c72937dfcfa0427a;hpb=8f9a9523d2161ee15975f5f9136ef80d4bfbf3e2;p=ardour.git diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 65e810f48e..b658487969 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -51,7 +51,6 @@ #include "ardour/analyser.h" #include "ardour/async_midi_port.h" #include "ardour/audio_buffer.h" -#include "ardour/audio_diskstream.h" #include "ardour/audio_port.h" #include "ardour/audio_track.h" #include "ardour/audioengine.h" @@ -66,6 +65,7 @@ #include "ardour/control_protocol_manager.h" #include "ardour/data_type.h" #include "ardour/debug.h" +#include "ardour/disk_reader.h" #include "ardour/directory_names.h" #ifdef USE_TRACKS_CODE_FEATURES #include "ardour/engine_state_controller.h" @@ -81,6 +81,7 @@ #include "ardour/midi_ui.h" #include "ardour/operations.h" #include "ardour/playlist.h" +#include "ardour/playlist_factory.h" #include "ardour/plugin.h" #include "ardour/plugin_insert.h" #include "ardour/process_thread.h" @@ -93,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" @@ -103,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" @@ -183,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) @@ -213,6 +218,7 @@ Session::Session (AudioEngine &eng, , _exporting (false) , _export_rolling (false) , _realtime_export (false) + , _region_export (false) , _export_preroll (0) , _export_latency (0) , _pre_export_mmc_enabled (false) @@ -220,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) @@ -239,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)) @@ -250,12 +255,12 @@ 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) , next_quarter_frame_to_send (-1) - , _frames_per_timecode_frame (0) + , _samples_per_timecode_frame (0) , _frames_per_hour (0) , _timecode_frames_per_hour (0) , last_timecode_valid (false) @@ -294,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) @@ -311,12 +321,13 @@ Session::Session (AudioEngine &eng, , _step_editors (0) , _suspend_timecode_transmission (0) , _speakers (new Speakers) - , ignore_route_processor_changes (false) + , _ignore_route_processor_changes (0) , midi_clock (0) , _scene_changer (0) , _midi_ports (0) , _mmc (0) , _vca_manager (new VCAManager (*this)) + , _selection (new CoreSelection (*this)) { uint32_t sr = 0; @@ -331,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 (); @@ -369,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); } @@ -403,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); @@ -414,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)); @@ -477,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")); } @@ -570,7 +615,6 @@ Session::immediately_post_engine () } try { - LocaleGuard lg; BootMessage (_("Set up LTC")); setup_ltc (); BootMessage (_("Set up Click")); @@ -605,8 +649,12 @@ Session::destroy () _state_of_the_state = StateOfTheState (CannotSave|Deletion); - /* stop autoconnecting */ - auto_connect_thread_terminate (); + { + 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 */ @@ -614,11 +662,19 @@ Session::destroy () 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 auto dis/connecting */ + auto_connect_thread_terminate (); + MIDI::Name::MidiPatchManager::instance().remove_search_path(session_directory().midi_patch_path()); _engine.remove_session (); @@ -633,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 (); @@ -644,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; @@ -682,6 +739,7 @@ Session::destroy () /* need to remove auditioner before monitoring section * otherwise it is re-connected */ + auditioner->drop_references (); auditioner.reset (); /* drop references to routes held by the monitoring section @@ -693,7 +751,7 @@ Session::destroy () routes.flush (); _bundles.flush (); - AudioDiskstream::free_working_buffers(); + DiskReader::free_working_buffers(); /* tell everyone who is still standing that we're about to die */ drop_references (); @@ -777,6 +835,7 @@ Session::destroy () case SessionEvent::Skip: case SessionEvent::PunchIn: case SessionEvent::PunchOut: + case SessionEvent::RecordStart: case SessionEvent::StopOnce: case SessionEvent::RangeStop: case SessionEvent::RangeLocate: @@ -797,6 +856,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 (); @@ -816,8 +887,10 @@ Session::setup_ltc () { Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); _ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); + // TODO use auto-connect thread somehow (needs a route currently) + // see note in Session::auto_connect_thread_run() why process lock is needed. + reconnect_ltc_input (); } - reconnect_ltc_input (); } if (state_tree && (child = find_named_node (*state_tree->root(), X_("LTC Out"))) != 0) { @@ -826,8 +899,9 @@ Session::setup_ltc () { Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); _ltc_output->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); + // TODO use auto-connect thread + reconnect_ltc_output (); } - reconnect_ltc_output (); } /* fix up names of LTC ports because we don't want the normal @@ -911,38 +985,11 @@ Session::setup_click_state (const XMLNode* node) } void -Session::get_physical_ports (vector& inputs, vector& outputs, DataType type, bool excluding) +Session::get_physical_ports (vector& inputs, vector& outputs, DataType type, + MidiPortFlags include, MidiPortFlags exclude) { - _engine.get_physical_inputs (type, inputs); - - if (excluding) { - /* rip out ControlOnly ports, and ALSA MIDI Through ports */ - - for (vector::iterator si = inputs.begin(); si != inputs.end(); ) { - if (PortManager::port_is_control_only (*si)) { - si = inputs.erase (si); - } else if ((*si).find (X_("Midi Through")) != string::npos || (*si).find (X_("Midi-Through")) != string::npos) { - si = inputs.erase (si); - } else { - ++si; - } - } - } - _engine.get_physical_outputs (type, outputs); - - if (excluding) { - /* rip out ControlOnly ports, and ALSA MIDI Through ports */ - - for (vector::iterator si = outputs.begin(); si != outputs.end(); ) { - if (PortManager::port_is_control_only (*si)) { - si = outputs.erase (si); - } else if ((*si).find (X_("Midi Through")) != string::npos || (*si).find (X_("Midi-Through")) != string::npos) { - si = outputs.erase (si); - } else { - ++si; - } - } - } + _engine.get_physical_inputs (type, inputs, include, exclude); + _engine.get_physical_outputs (type, outputs, include, exclude); } void @@ -965,7 +1012,10 @@ Session::setup_bundles () vector outputs[DataType::num_types]; for (uint32_t i = 0; i < DataType::num_types; ++i) { - get_physical_ports (inputs[i], outputs[i], DataType (DataType::Symbol (i)), true); + get_physical_ports (inputs[i], outputs[i], DataType (DataType::Symbol (i)), + MidiPortFlags (0), /* no specific inclusions */ + MidiPortFlags (MidiPortControl|MidiPortVirtual) /* exclude control & virtual ports */ + ); } /* Create a set of Bundle objects that map @@ -1049,6 +1099,7 @@ Session::setup_bundles () for (uint32_t np = 0; np < inputs[DataType::MIDI].size(); ++np) { string n = inputs[DataType::MIDI][np]; + std::string pn = _engine.get_pretty_name_by_name (n); if (!pn.empty()) { n = pn; @@ -1151,7 +1202,7 @@ Session::remove_monitor_section () boost::shared_ptr r = routes.reader (); - PBD::Unwinder uw (ignore_route_processor_changes, true); + ProcessorChangeBlocker pcb (this, false); for (RouteList::iterator x = r->begin(); x != r->end(); ++x) { @@ -1311,7 +1362,7 @@ Session::add_monitor_section () boost::shared_ptr rls = routes.reader (); - PBD::Unwinder uw (ignore_route_processor_changes, true); + ProcessorChangeBlocker pcb (this, false /* XXX */); for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) { @@ -1435,7 +1486,7 @@ Session::reset_monitor_section () boost::shared_ptr rls = routes.reader (); - PBD::Unwinder uw (ignore_route_processor_changes, true); + ProcessorChangeBlocker pcb (this, false); for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) { @@ -1449,6 +1500,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 () { @@ -1469,7 +1547,6 @@ Session::hookup_io () if (a->init()) { throw failed_constructor (); } - a->use_new_diskstream (); auditioner = a; } @@ -1622,7 +1699,7 @@ Session::auto_loop_changed (Location* location) } else if (Config->get_seamless_loop() && !loop_changing) { - // schedule a locate-roll to refill the diskstreams at the + // schedule a locate-roll to refill the disk readers at the // previous loop end loop_changing = true; @@ -1698,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) { @@ -1887,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()) { @@ -1896,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); } @@ -2015,6 +2104,7 @@ Session::disable_record (bool rt_context, bool force) if (!rt_context) { remove_pending_capture_state (); } + unset_preroll_record_punch (); } } @@ -2032,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; @@ -2041,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 { @@ -2061,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 */ @@ -2091,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; + } } } @@ -2115,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) { @@ -2387,12 +2509,12 @@ Session::find_route_name (string const & base, uint32_t& id, string& name, bool before anything else. */ - for (vector::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) { - if (base == *reserved) { + for (map::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) { + if (base == reserved->first) { /* Check if this reserved name already exists, and if so, disallow it without a numeric suffix. */ - if (route_by_name (*reserved)) { + if (!reserved->second || route_by_name (reserved->first)) { definitely_add_number = true; if (id < 1) { id = 1; @@ -2402,8 +2524,11 @@ Session::find_route_name (string const & base, uint32_t& id, string& name, bool } } - if (!definitely_add_number && route_by_name (base) == 0) { - /* juse use the base */ + /* if we have "base 1" already, it doesn't make sense to add "base" + * if "base 1" has been deleted, adding "base" is no worse than "base 1" + */ + if (!definitely_add_number && route_by_name (base) == 0 && (route_by_name (string_compose("%1 1", base)) == 0)) { + /* just use the base */ name = base; return true; } @@ -2432,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(); } } @@ -2464,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; @@ -2493,12 +2621,10 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, goto failed; } - if (Profile->get_mixbus ()) { + if (strict_io) { track->set_strict_io (true); } - track->use_new_diskstream(); - BOOST_MARK_TRACK (track); { @@ -2514,14 +2640,10 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, } } - track->non_realtime_input_change(); - if (route_group) { route_group->add (track); } - track->DiskstreamChanged.connect_same_thread (*this, boost::bind (&Session::resort_routes, this)); - new_routes.push_back (track); ret.push_back (track); } @@ -2552,12 +2674,23 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, if (instrument) { for (RouteList::iterator r = new_routes.begin(); r != new_routes.end(); ++r) { PluginPtr plugin = instrument->load (*this); + if (!plugin) { + warning << "Failed to add Synth Plugin to newly created track." << endmsg; + continue; + } 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); + } } } } @@ -2566,7 +2699,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; @@ -2589,7 +2723,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); } @@ -2640,11 +2774,23 @@ Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name if (instrument) { for (RouteList::iterator r = ret.begin(); r != ret.end(); ++r) { PluginPtr plugin = instrument->load (*this); + if (!plugin) { + warning << "Failed to add Synth Plugin to newly created track." << endmsg; + continue; + } 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); + } } } } @@ -2962,9 +3108,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; @@ -2979,7 +3152,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; } @@ -3040,8 +3217,6 @@ Session::new_audio_track (int input_channels, int output_channels, RouteGroup* r } } - track->use_new_diskstream(); - BOOST_MARK_TRACK (track); { @@ -3068,10 +3243,6 @@ Session::new_audio_track (int input_channels, int output_channels, RouteGroup* r route_group->add (track); } - track->non_realtime_input_change(); - - track->DiskstreamChanged.connect_same_thread (*this, boost::bind (&Session::resort_routes, this)); - new_routes.push_back (track); ret.push_back (track); } @@ -3252,17 +3423,27 @@ Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t i /* set this name in the XML description that we are about to use */ - bool rename_playlist; - switch (pd) { - case NewPlaylist: - rename_playlist = true; - break; - default: - case CopyPlaylist: - case SharePlaylist: - rename_playlist = false; + if (pd == CopyPlaylist) { + 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); + // Use same name as Route::set_name_in_state so playlist copy + // is picked up when creating the Route in XMLRouteFactory below + 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()); + } } + bool rename_playlist = (pd == CopyPlaylist || pd == NewPlaylist); + Route::set_name_in_state (node_copy, name, rename_playlist); /* trim bitslots from listen sends so that new ones are used */ @@ -3278,12 +3459,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; } } @@ -3291,20 +3472,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 @@ -3313,11 +3492,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) { @@ -3340,21 +3523,6 @@ Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t i route->output()->changed (change, this); } - boost::shared_ptr track; - - if ((track = boost::dynamic_pointer_cast (route))) { - switch (pd) { - case NewPlaylist: - track->use_new_playlist (); - break; - case CopyPlaylist: - track->use_copy_playlist (); - break; - case SharePlaylist: - break; - } - }; - ret.push_back (route); } @@ -3425,8 +3593,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 @@ -3439,83 +3607,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))); + 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) { @@ -3624,19 +3801,32 @@ 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; } - (*iter)->solo_control()->set_value (0.0, Controllable::NoGroup); + /* speed up session deletion, don't do the solo dance */ + if (0 == (_state_of_the_state & Deletion)) { + (*iter)->solo_control()->set_value (0.0, Controllable::NoGroup); + } + + if ((*iter)->mute_control()->muted ()) { + mute_changed = true; + } rs->remove (*iter); @@ -3673,7 +3863,7 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) /* if the monitoring section had a pointer to this route, remove it */ if (_monitor_out && !(*iter)->is_master() && !(*iter)->is_monitor()) { Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - PBD::Unwinder uw (ignore_route_processor_changes, true); + ProcessorChangeBlocker pcb (this, false); (*iter)->remove_aux_or_listen (_monitor_out); } @@ -3689,6 +3879,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(); @@ -3714,6 +3908,19 @@ 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 */ @@ -3722,11 +3929,13 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) (*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 */ @@ -3748,6 +3957,7 @@ Session::remove_route (boost::shared_ptr route) void Session::route_mute_changed () { + MuteChanged (); /* EMIT SIGNAL */ set_dirty (); } @@ -3800,8 +4010,6 @@ Session::route_listen_changed (Controllable::GroupControlDisposition group_overr _listen_cnt--; } - - update_route_solo_state (); } void @@ -3835,7 +4043,7 @@ Session::route_solo_isolated_changed (boost::weak_ptr wpr) void Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlDisposition group_override, boost::weak_ptr wpr) { - DEBUG_TRACE (DEBUG::Solo, string_compose ("route solo change, self = %1\n", self_solo_changed)); + DEBUG_TRACE (DEBUG::Solo, string_compose ("route solo change, self = %1, update\n", self_solo_changed)); boost::shared_ptr route (wpr.lock()); @@ -3851,21 +4059,13 @@ Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlD DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: self %2 masters %3 transition %4\n", route->name(), route->self_soloed(), route->solo_control()->get_masters_value(), route->solo_control()->transitioned_into_solo())); if (route->solo_control()->transitioned_into_solo() == 0) { - /* route solo changed by upstream/downstream; not interesting + /* route solo changed by upstream/downstream or clear all solo state; not interesting to Session. */ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 not self-soloed nor soloed by master (%2), ignoring\n", route->name(), route->solo_control()->get_masters_value())); return; } - if (route->solo_control()->transitioned_into_solo() == 0) { - /* reason for being soloed changed (e.g. master went away, we - * took over the master state), but actual status did - * not. nothing to do. - */ - DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: solo change was change in reason, not status\n", route->name())); - } - boost::shared_ptr r = routes.reader (); int32_t delta = route->solo_control()->transitioned_into_solo (); @@ -3888,6 +4088,8 @@ Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlD RouteGroup* rg = route->route_group (); const bool group_already_accounted_for = (group_override == Controllable::ForGroup); + DEBUG_TRACE (DEBUG::Solo, string_compose ("propagate to session, group accounted for ? %1\n", group_already_accounted_for)); + if (delta == 1 && Config->get_exclusive_solo()) { /* new solo: disable all other solos, but not the group if its solo-enabled */ @@ -4001,8 +4203,6 @@ Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlD DEBUG_TRACE (DEBUG::Solo, "propagation complete\n"); - update_route_solo_state (r); - /* now notify that the mute state of the routes not involved in the signal pathway of the just-solo-changed route may have altered. */ @@ -4010,11 +4210,10 @@ Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlD 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_control()->Changed (false, Controllable::NoGroup); + /* Session will emit SoloChanged() after all solo changes are + * complete, which should be used by UIs to update mute status + */ } - - SoloChanged (); /* EMIT SIGNAL */ - set_dirty(); } void @@ -4034,13 +4233,13 @@ Session::update_route_solo_state (boost::shared_ptr r) for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { if ((*i)->can_solo()) { if (Config->get_solo_control_is_listen_control()) { - if ((*i)->self_soloed() || (*i)->solo_control()->get_masters_value()) { + if ((*i)->solo_control()->soloed_by_self_or_masters()) { listeners++; something_listening = true; } } else { (*i)->set_listen (false); - if ((*i)->can_solo() && ((*i)->self_soloed() || (*i)->solo_control()->get_masters_value())) { + if ((*i)->can_solo() && (*i)->solo_control()->soloed_by_self_or_masters()) { something_soloed = true; } } @@ -4070,6 +4269,65 @@ Session::update_route_solo_state (boost::shared_ptr r) DEBUG_TRACE (DEBUG::Solo, string_compose ("solo state updated by session, soloed? %1 listeners %2 isolated %3\n", something_soloed, listeners, isolated)); + + + SoloChanged (); /* EMIT SIGNAL */ + 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 @@ -4082,6 +4340,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 { @@ -4101,11 +4368,11 @@ Session::io_name_is_legal (const std::string& name) const { boost::shared_ptr r = routes.reader (); - for (vector::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) { - if (name == *reserved) { - if (!route_by_name (*reserved)) { - /* first instance of a reserved name is allowed */ - return true; + 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 for some */ + return reserved->second; } /* all other instances of a reserved name are not allowed */ return false; @@ -4233,34 +4500,35 @@ Session::route_by_id (PBD::ID id) const return boost::shared_ptr ((Route*) 0); } -boost::shared_ptr -Session::processor_by_id (PBD::ID id) const + +boost::shared_ptr +Session::stripable_by_id (PBD::ID id) const { - boost::shared_ptr r = routes.reader (); + StripableList sl; + get_stripables (sl); - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - boost::shared_ptr p = (*i)->Route::processor_by_id (id); - if (p) { - return p; + for (StripableList::const_iterator s = sl.begin(); s != sl.end(); ++s) { + if ((*s)->id() == id) { + return *s; } } - return boost::shared_ptr (); + return boost::shared_ptr(); } -boost::shared_ptr -Session::track_by_diskstream_id (PBD::ID id) const +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 t = boost::dynamic_pointer_cast (*i); - if (t && t->using_diskstream_id (id)) { - return t; + boost::shared_ptr p = (*i)->Route::processor_by_id (id); + if (p) { + return p; } } - return boost::shared_ptr (); + return boost::shared_ptr (); } boost::shared_ptr @@ -4276,7 +4544,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) { @@ -4311,28 +4579,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 () @@ -4340,16 +4602,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); } } @@ -4676,13 +4938,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 @@ -4983,17 +5248,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); @@ -5008,38 +5268,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 @@ -5191,6 +5449,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(); } @@ -5200,6 +5461,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(); } @@ -5215,7 +5479,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; } @@ -5231,7 +5495,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 (); } } @@ -5243,6 +5507,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 = {} }" @@ -5259,8 +5524,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" @@ -5363,7 +5627,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*/ } @@ -5389,6 +5658,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; @@ -5425,18 +5699,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 { @@ -5459,12 +5721,6 @@ Session::graph_reordered () return; } - /* every track/bus asked for this to be handled but it was deferred because - we were connecting. do it now. - */ - - request_input_change_handling (); - resort_routes (); /* force all diskstreams to update their capture offset values to @@ -5475,7 +5731,7 @@ Session::graph_reordered () for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); if (tr) { - tr->set_capture_offset (); + tr->update_latency_information (); } } } @@ -5589,21 +5845,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 (); } } @@ -6103,6 +6349,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 { @@ -6170,6 +6422,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) { @@ -6233,8 +6502,8 @@ Session::update_route_record_state () void Session::listen_position_changed () { + ProcessorChangeBlocker pcb (this); boost::shared_ptr r = routes.reader (); - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { (*i)->listen_position_changed (); } @@ -6244,14 +6513,18 @@ void Session::solo_control_mode_changed () { if (soloing() || listening()) { - /* We can't use ::clear_all_solo_state() here because during - session loading at program startup, that will queue a call - to rt_clear_all_solo_state() that will not execute until - AFTER solo states have been established (thus throwing away - the session's saved solo state). So just explicitly turn - them all off. - */ - set_controls (route_list_to_control_list (get_routes(), &Stripable::solo_control), 0.0, Controllable::NoGroup); + if (loading()) { + /* We can't use ::clear_all_solo_state() here because during + session loading at program startup, that will queue a call + to rt_clear_all_solo_state() that will not execute until + AFTER solo states have been established (thus throwing away + the session's saved solo state). So just explicitly turn + them all off. + */ + set_controls (route_list_to_control_list (get_routes(), &Stripable::solo_control), 0.0, Controllable::NoGroup); + } else { + clear_all_solo_state (get_routes()); + } } } @@ -6275,6 +6548,10 @@ Session::route_removed_from_route_group (RouteGroup* rg, boost::weak_ptr { update_route_record_state (); RouteRemovedFromRouteGroup (rg, r); /* EMIT SIGNAL */ + + if (!rg->has_control_master () && !rg->has_subgroup () && rg->empty()) { + remove_route_group (*rg); + } } boost::shared_ptr @@ -6285,9 +6562,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; @@ -6353,7 +6629,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); } @@ -6575,6 +6851,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; @@ -6628,7 +6907,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 ()); } } @@ -6650,7 +6930,7 @@ Session::post_capture_latency () for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); if (tr) { - tr->set_capture_offset (); + tr->update_latency_information (); } } } @@ -6734,7 +7014,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; @@ -6758,7 +7039,7 @@ Session::update_latency_compensation (bool force_whole_graph) if (!tr) { continue; } - tr->set_capture_offset (); + tr->update_latency_information (); } } @@ -6783,7 +7064,6 @@ Session::notify_presentation_info_change () return; } - PresentationInfo::Change (); /* EMIT SIGNAL */ reassign_track_numbers(); #ifdef USE_TRACKS_CODE_FEATURES @@ -6804,13 +7084,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 @@ -6951,7 +7232,12 @@ Session::auto_connect (const AutoConnectRequest& ar) vector physinputs; vector physoutputs; - get_physical_ports (physinputs, physoutputs, *t, true); + + /* for connecting track inputs we only want MIDI ports marked + * for "music". + */ + + get_physical_ports (physinputs, physoutputs, *t, MidiPortMusic); if (!physinputs.empty() && ar.connect_inputs) { uint32_t nphysical_in = physinputs.size(); @@ -7005,7 +7291,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; } @@ -7013,19 +7299,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); @@ -7034,7 +7319,14 @@ Session::auto_connect_thread_terminate () } } - auto_connect_thread_wakeup (); + /* cannot use auto_connect_thread_wakeup() because that is allowed to + * fail to wakeup the thread. + */ + + 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); void *status; pthread_join (_auto_connect_thread, &status); @@ -7056,7 +7348,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 ?? @@ -7080,8 +7372,8 @@ Session::auto_connect_thread_run () /* this is only used for updating plugin latencies, the * graph does not change. so it's safe in general. * BUT.. - * .. update_latency_compensation () entails set_capture_offset() - * which calls Diskstream::set_capture_offset () which + * .. update_latency_compensation () entails Track::update_latency_information() + * which calls DiskWriter::set_capture_offset () which * modifies the capture offset... which can be a proplem * in "prepare_to_stop" */ @@ -7090,14 +7382,11 @@ Session::auto_connect_thread_run () } } - std::cerr << "Autoconnect thread checking port deletions ...\n"; - - RingBuffer& ports (AudioEngine::instance()->port_deletions_pending()); - Port* p; - - while (ports.read (&p, 1) == 1) { - std::cerr << "autoconnect deletes " << p->name() << std::endl; - delete p; + { + // 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 (); } pthread_cond_wait (&_auto_connect_cond, &_auto_connect_mutex);