#include "pbd/basename.h"
#include "pbd/convert.h"
-#include "pbd/convert.h"
#include "pbd/error.h"
#include "pbd/file_utils.h"
#include "pbd/md5.h"
#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"
#include "LuaBridge/LuaBridge.h"
-#include "i18n.h"
+#include "pbd/i18n.h"
#include <glibmm/checksum.h>
, _record_status (Disabled)
, _transport_frame (0)
, _session_range_location (0)
+ , _session_range_end_is_free (true)
, _slave (0)
, _silent (false)
, _transport_speed (0)
, post_export_position (0)
, _exporting (false)
, _export_rolling (false)
+ , _realtime_export (false)
+ , _region_export (false)
, _export_preroll (0)
+ , _export_latency (0)
, _pre_export_mmc_enabled (false)
, _name (snapshot_name)
, _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)
, 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))
, _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)
, _reconnecting_routes_in_progress (false)
, _route_deletion_in_progress (false)
, destructive_index (0)
- , _latency_recompute_pending (0)
, _track_number_decimals(1)
, default_fade_steepness (0)
, default_fade_msecs (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)
, _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)
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 ();
_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));
}
#endif
+ ensure_subdirs (); // archived or zipped sessions may lack peaks/ analysis/ etc
+
_is_new = false;
session_loaded ();
_state_of_the_state = StateOfTheState (CannotSave|Deletion);
- /* stop autoconnecting */
- auto_connect_thread_terminate ();
-
/* disconnect from any and all signals that we are connected to */
Port::PortSignalDrop (); /* EMIT SIGNAL */
ControlProtocolManager::instance().drop_protocols ();
+ /* stop autoconnecting */
+ auto_connect_thread_terminate ();
+
MIDI::Name::MidiPatchManager::instance().remove_search_path(session_directory().midi_patch_path());
_engine.remove_session ();
case SessionEvent::Skip:
case SessionEvent::PunchIn:
case SessionEvent::PunchOut:
+ case SessionEvent::RecordStart:
case SessionEvent::StopOnce:
case SessionEvent::RangeStop:
case SessionEvent::RangeLocate:
{
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) {
{
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
}
}
+void
+Session::get_physical_ports (vector<string>& inputs, vector<string>& outputs, DataType type,
+ MidiPortFlags include, MidiPortFlags exclude)
+{
+ _engine.get_physical_inputs (type, inputs, include, exclude);
+ _engine.get_physical_outputs (type, outputs, include, exclude);
+}
+
void
Session::setup_bundles ()
{
vector<string> inputs[DataType::num_types];
vector<string> outputs[DataType::num_types];
+
for (uint32_t i = 0; i < DataType::num_types; ++i) {
- _engine.get_physical_inputs (DataType (DataType::Symbol (i)), inputs[i]);
- _engine.get_physical_outputs (DataType (DataType::Symbol (i)), outputs[i]);
+ 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
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;
boost::shared_ptr<RouteList> r = routes.reader ();
- PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+ ProcessorChangeBlocker pcb (this, false);
for (RouteList::iterator x = r->begin(); x != r->end(); ++x) {
boost::shared_ptr<RouteList> rls = routes.reader ();
- PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+ ProcessorChangeBlocker pcb (this, false /* XXX */);
for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) {
boost::shared_ptr<RouteList> rls = routes.reader ();
- PBD::Unwinder<bool> uw (ignore_route_processor_changes, true);
+ ProcessorChangeBlocker pcb (this, false);
for (RouteList::iterator x = rls->begin(); x != rls->end(); ++x) {
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) {
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()) {
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);
}
if (!rt_context) {
remove_pending_capture_state ();
}
+ unset_preroll_record_punch ();
}
}
}
void
-Session::maybe_enable_record ()
+Session::maybe_enable_record (bool rt_context)
{
if (_step_editors > 0) {
return;
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 {
Session::audible_frame () const
{
framepos_t ret;
- framepos_t tf;
- framecnt_t offset;
- offset = worst_playback_latency ();
+ frameoffset_t offset = worst_playback_latency (); // - _engine.samples_since_cycle_start ();
+ offset *= transport_speed ();
if (synced_to_engine()) {
/* Note: this is basically just sync-to-JACK */
- tf = _engine.transport_frame();
+ ret = _engine.transport_frame();
} else {
- tf = _transport_frame;
+ ret = _transport_frame;
}
- ret = tf;
-
- if (!non_realtime_work_pending()) {
-
- /* MOVING */
+ if (transport_rolling()) {
+ ret -= offset;
/* Check to see if we have passed the first guaranteed
- audible frame past our last start position. if not,
- return that last start point because in terms
- of audible frames, we have not moved yet.
-
- `Start position' in this context means the time we last
- either started, located, or changed transport direction.
- */
+ * audible frame past our last start position. if not,
+ * return that last start point because in terms
+ * of audible frames, we have not moved yet.
+ *
+ * `Start position' in this context means the time we last
+ * either started, located, or changed transport direction.
+ */
if (_transport_speed > 0.0f) {
if (!play_loop || !have_looped) {
- if (tf < _last_roll_or_reversal_location + offset) {
+ if (ret < _last_roll_or_reversal_location) {
return _last_roll_or_reversal_location;
}
+ } else {
+ // latent loops
+ Location *location = _locations->auto_loop_location();
+ frameoffset_t lo = location->start() - ret;
+ if (lo > 0) {
+ ret = location->end () - lo;
+ }
}
-
- /* forwards */
- ret -= offset;
-
} else if (_transport_speed < 0.0f) {
/* XXX wot? no backward looping? */
- if (tf > _last_roll_or_reversal_location - offset) {
+ if (ret > _last_roll_or_reversal_location) {
return _last_roll_or_reversal_location;
- } else {
- /* backwards */
- ret += offset;
}
}
}
- return ret;
+ 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
before anything else.
*/
- for (vector<string>::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) {
- if (base == *reserved) {
+ for (map<string,bool>::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;
}
}
- 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;
}
* @param instrument plugin info for the instrument to insert pre-fader, if any
*/
list<boost::shared_ptr<MidiTrack> >
-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<PluginInfo> 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;
goto failed;
}
- if (Profile->get_mixbus ()) {
+ if (strict_io) {
track->set_strict_io (true);
}
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<Processor> p (new PluginInsert (*this, plugin));
- (*r)->add_processor (p, PreFader);
+ boost::shared_ptr<PluginInsert> 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);
+ }
}
}
}
}
RouteList
-Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name_template, boost::shared_ptr<PluginInfo> instrument, Plugin::PresetRecord* pset,
+Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name_template, bool strict_io,
+ boost::shared_ptr<PluginInfo> instrument, Plugin::PresetRecord* pset,
PresentationInfo::Flag flag, PresentationInfo::order_t order)
{
string bus_name;
goto failure;
}
- if (Profile->get_mixbus ()) {
+ if (strict_io) {
bus->set_strict_io (true);
}
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<Processor> p (new PluginInsert (*this, plugin));
- (*r)->add_processor (p, PreFader);
+ boost::shared_ptr<PluginInsert> 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);
+ }
}
}
}
}
RouteList
-Session::new_route_from_template (uint32_t how_many, const std::string& template_path, const std::string& name_base, PlaylistDisposition pd)
+Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t insert_at, const std::string& template_path, const std::string& name_base,
+ PlaylistDisposition pd)
{
XMLTree tree;
return RouteList();
}
- return new_route_from_template (how_many, *tree.root(), name_base, pd);
+ return new_route_from_template (how_many, insert_at, *tree.root(), name_base, pd);
}
RouteList
-Session::new_route_from_template (uint32_t how_many, XMLNode& node, const std::string& name_base, PlaylistDisposition pd)
+Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t insert_at, XMLNode& node, const std::string& name_base, PlaylistDisposition pd)
{
RouteList ret;
uint32_t number = 0;
/* 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> 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> 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 */
route->output()->changed (change, this);
}
- boost::shared_ptr<Track> track;
-
- if ((track = boost::dynamic_pointer_cast<Track> (route))) {
- switch (pd) {
- case NewPlaylist:
- track->use_new_playlist ();
- break;
- case CopyPlaylist:
- track->use_copy_playlist ();
- break;
- case SharePlaylist:
- break;
- }
- };
-
ret.push_back (route);
}
if (!ret.empty()) {
StateProtector sp (this);
if (Profile->get_trx()) {
- add_routes (ret, false, false, false, PresentationInfo::max_order);
+ add_routes (ret, false, false, false, insert_at);
} else {
- add_routes (ret, true, true, false, PresentationInfo::max_order);
+ add_routes (ret, true, true, false, insert_at);
}
IO::enable_connecting ();
}
}
}
+ /* auditioner and monitor routes are not part of the order */
+ if (auditioner) {
+ assert (n_routes > 0);
+ --n_routes;
+ }
+ if (_monitor_out) {
+ assert (n_routes > 0);
+ --n_routes;
+ }
+
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());
- for (RouteList::iterator x = new_routes.begin(); x != new_routes.end(); ++x, ++added) {
+ {
+ PresentationInfo::ChangeSuspender cs;
- boost::weak_ptr<Route> wpr (*x);
- boost::shared_ptr<Route> 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<Route> wpr (*x);
+ boost::shared_ptr<Route> 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<Track> tr = boost::dynamic_pointer_cast<Track> (r);
- if (tr) {
- tr->PlaylistChanged.connect_same_thread (*this, boost::bind (&Session::track_playlist_changed, this, boost::weak_ptr<Track> (tr)));
- track_playlist_changed (boost::weak_ptr<Track> (tr));
- tr->rec_enable_control()->Changed.connect_same_thread (*this, boost::bind (&Session::update_route_record_state, this));
-
- boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (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<Route>(mt)));
+ if (r->is_monitor()) {
+ _monitor_out = r;
+ }
+
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (r);
+ if (tr) {
+ tr->PlaylistChanged.connect_same_thread (*this, boost::bind (&Session::track_playlist_changed, this, boost::weak_ptr<Track> (tr)));
+ track_playlist_changed (boost::weak_ptr<Track> (tr));
+ tr->rec_enable_control()->Changed.connect_same_thread (*this, boost::bind (&Session::update_route_record_state, this));
+
+ boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (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<Route>(mt)));
+ mt->presentation_info().PropertyChanged.connect_same_thread (*this, boost::bind (&Session::midi_track_presentation_info_changed, this, _1, boost::weak_ptr<MidiTrack>(mt)));
+ }
}
- }
- if (!r->presentation_info().special()) {
+ if (!r->presentation_info().special()) {
- DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("checking PI state for %1\n", r->name()));
+ DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("checking PI state for %1\n", r->name()));
- /* presentation info order may already have been set from XML */
+ /* presentation info order may already have been set from XML */
- if (!r->presentation_info().order_set()) {
+ if (!r->presentation_info().order_set()) {
+ /* this is only useful for headless sessions,
+ * Editor::add_routes() and Mixer_UI::add_routes() will
+ * override it following the RouteAdded signal.
+ *
+ * Also routes should be sorted before VCAs (like the GUI does).
+ * Session::ensure_route_presentation_info_gap() does not special case VCAs either.
+ *
+ * ... but not to worry, the GUI's
+ * gtk2_ardour/route_sorter.h and various ::sync_presentation_info_from_treeview()
+ * handle this :)
+ */
- if (order == PresentationInfo::max_order) {
- /* just add to the end */
- r->set_presentation_order_explicit (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));
+ 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 ();
+ }
}
if (_monitor_out && IO::connecting_legal) {
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);
+ }
rs->remove (*iter);
/* 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<bool> uw (ignore_route_processor_changes, true);
+ ProcessorChangeBlocker pcb (this, false);
(*iter)->remove_aux_or_listen (_monitor_out);
}
*/
for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) {
+ cerr << "Drop references to " << (*iter)->name() << endl;
(*iter)->drop_references ();
}
return;
}
- PresentationInfo::Change(); /* EMIT SIGNAL */
+ PropertyChange so;
+ so.add (Properties::selected);
+ so.add (Properties::order);
+ PresentationInfo::Change (PropertyChange (so));
/* save the new state of the world */
_listen_cnt--;
}
-
- update_route_solo_state ();
}
void
void
Session::route_solo_changed (bool self_solo_changed, Controllable::GroupControlDisposition group_override, boost::weak_ptr<Route> 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> route (wpr.lock());
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<RouteList> r = routes.reader ();
int32_t delta = route->solo_control()->transitioned_into_solo ();
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 */
if ((*i)->solo_isolate_control()->solo_isolated() || !(*i)->can_solo()) {
/* route does not get solo propagated to it */
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 excluded from solo because iso = %2 can_solo = %3\n", (*i)->name(), (*i)->solo_isolate_control()->solo_isolated(),
+ (*i)->can_solo()));
continue;
}
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.
*/
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
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;
}
}
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();
}
void
{
boost::shared_ptr<RouteList> r = routes.reader ();
- for (vector<string>::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) {
- if (name == *reserved) {
- if (!route_by_name (*reserved)) {
+ for (map<string,bool>::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;
}
sl.sort (Stripable::PresentationOrderSorter());
for (StripableList::const_iterator s = sl.begin(); s != sl.end(); ++s) {
+
+ if ((*s)->presentation_info().hidden()) {
+ /* if the caller didn't explicitly ask for hidden
+ stripables, ignore hidden ones. This matches
+ the semantics of the pre-PresentationOrder
+ "get by RID" logic of Ardour 4.x and earlier.
+
+ XXX at some point we should likely reverse
+ the logic of the flags, because asking for "the
+ hidden stripables" is not going to be common,
+ whereas asking for visible ones is normal.
+ */
+
+ if (! (flags & PresentationInfo::Hidden)) {
+ continue;
+ }
+ }
+
if ((*s)->presentation_info().flag_match (flags)) {
if (match_cnt++ == n) {
return *s;
return boost::shared_ptr<Stripable>();
}
-boost::shared_ptr<Route>
-Session::route_by_selected_count (uint32_t id) const
-{
- boost::shared_ptr<RouteList> r = routes.reader ();
-
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- /* NOT IMPLEMENTED */
- }
-
- return boost::shared_ptr<Route> ((Route*) 0);
-}
-
struct PresentationOrderSorter {
bool operator() (boost::shared_ptr<Stripable> a, boost::shared_ptr<Stripable> b) {
if (a->presentation_info().special() && !b->presentation_info().special()) {
}
};
+boost::shared_ptr<Route>
+Session::route_by_selected_count (uint32_t id) const
+{
+ RouteList r (*(routes.reader ()));
+ PresentationOrderSorter sorter;
+ r.sort (sorter);
+
+ RouteList::iterator i;
+
+ for (i = r.begin(); i != r.end(); ++i) {
+ if ((*i)->presentation_info().selected()) {
+ if (id == 0) {
+ return *i;
+ }
+ --id;
+ }
+ }
+
+ return boost::shared_ptr<Route> ();
+}
+
void
Session::reassign_track_numbers ()
{
_session_range_location->set_start (a);
}
- if (b > _session_range_location->end()) {
+ if (_session_range_end_is_free && (b > _session_range_location->end())) {
_session_range_location->set_end (b);
}
}
}
+void
+Session::set_end_is_free (bool yn)
+{
+ _session_range_end_is_free = yn;
+}
+
void
Session::playlist_ranges_moved (list<Evoral::RangeMove<framepos_t> > const & ranges)
{
tbl_arg[(*i)->name] = (*i)->value;
}
(*_lua_add)(name, bytecode, tbl_arg); // throws luabridge::LuaException
+ lm.release();
+
+ LuaScriptsChanged (); /* EMIT SIGNAL */
set_dirty();
}
Glib::Threads::Mutex::Lock lm (lua_lock);
(*_lua_del)(name); // throws luabridge::LuaException
lua.collect_garbage ();
+ lm.release();
+
+ LuaScriptsChanged (); /* EMIT SIGNAL */
set_dirty();
}
" 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"
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 ();
}
}
void
Session::listen_position_changed ()
{
+ ProcessorChangeBlocker pcb (this);
boost::shared_ptr<RouteList> r = routes.reader ();
-
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
(*i)->listen_position_changed ();
}
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());
+ }
}
}
{
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<RouteList>
}
void
-Session::goto_start ()
+Session::goto_start (bool and_roll)
{
if (_session_range_location) {
- request_locate (_session_range_location->start(), false);
+ request_locate (_session_range_location->start(), and_roll);
} else {
- request_locate (0, false);
+ request_locate (0, and_roll);
}
}
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);
}
if (l && l->start() == old) {
l->set_start (s->start(), true);
}
+ set_dirty ();
}
void
if (l && l->end() == old) {
l->set_end (s->end(), true);
}
+ set_dirty ();
}
std::vector<std::string>
return;
}
- PresentationInfo::Change (); /* EMIT SIGNAL */
reassign_track_numbers();
#ifdef USE_TRACKS_CODE_FEATURES
input_start, output_start,
input_offset, output_offset));
+ auto_connect_thread_wakeup ();
+}
+
+void
+Session::auto_connect_thread_wakeup ()
+{
if (pthread_mutex_trylock (&_auto_connect_mutex) == 0) {
pthread_cond_signal (&_auto_connect_cond);
pthread_mutex_unlock (&_auto_connect_mutex);
Session::queue_latency_recompute ()
{
g_atomic_int_inc (&_latency_recompute_pending);
- if (pthread_mutex_trylock (&_auto_connect_mutex) == 0) {
- pthread_cond_signal (&_auto_connect_cond);
- pthread_mutex_unlock (&_auto_connect_mutex);
- }
+ auto_connect_thread_wakeup ();
}
void
vector<string> physinputs;
vector<string> physoutputs;
- _engine.get_physical_outputs (*t, physoutputs);
- _engine.get_physical_inputs (*t, physinputs);
+
+ /* 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();
void
Session::auto_connect_thread_start ()
{
- if (_ac_thread_active) {
+ if (g_atomic_int_get (&_ac_thread_active)) {
return;
}
_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);
}
}
- if (pthread_mutex_lock (&_auto_connect_mutex) == 0) {
- pthread_cond_signal (&_auto_connect_cond);
- pthread_mutex_unlock (&_auto_connect_mutex);
- }
+ /* 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);
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 ??
}
}
+ {
+ // 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);
}
pthread_mutex_unlock (&_auto_connect_mutex);
}
+
+void
+Session::cancel_all_solo ()
+{
+ StripableList sl;
+
+ get_stripables (sl);
+
+ set_controls (stripable_list_to_control_list (sl, &Stripable::solo_control), 0.0, Controllable::NoGroup);
+ clear_all_solo_state (routes.reader());
+}