#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"
#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"
#include "ardour/revision.h"
#include "ardour/route_graph.h"
#include "ardour/route_group.h"
+#include "ardour/rt_tasklist.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/slave.h"
#include "ardour/solo_isolate_control.h"
#include "ardour/source_factory.h"
#include "ardour/speakers.h"
#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"
PBD::Signal1<int,uint32_t> Session::AudioEngineSetupRequired;
PBD::Signal1<void,std::string> Session::Dialog;
PBD::Signal0<int> Session::AskAboutPendingState;
-PBD::Signal2<int, framecnt_t, framecnt_t> Session::AskAboutSampleRateMismatch;
-PBD::Signal2<void, framecnt_t, framecnt_t> Session::NotifyAboutSampleRateMismatch;
+PBD::Signal2<int, samplecnt_t, samplecnt_t> Session::AskAboutSampleRateMismatch;
+PBD::Signal2<void, samplecnt_t, samplecnt_t> Session::NotifyAboutSampleRateMismatch;
PBD::Signal0<void> Session::SendFeedback;
PBD::Signal3<int,Session*,std::string,DataType> Session::MissingFile;
-PBD::Signal1<void, framepos_t> Session::StartTimeChanged;
-PBD::Signal1<void, framepos_t> Session::EndTimeChanged;
+PBD::Signal1<void, samplepos_t> Session::StartTimeChanged;
+PBD::Signal1<void, samplepos_t> Session::EndTimeChanged;
PBD::Signal2<void,std::string, std::string> Session::Exported;
PBD::Signal1<int,boost::shared_ptr<Playlist> > Session::AskAboutPlaylistDeletion;
PBD::Signal0<void> Session::Quit;
PBD::Signal0<void> Session::SuccessfulGraphSort;
PBD::Signal2<void,std::string,std::string> Session::VersionMismatch;
-const framecnt_t Session::bounce_chunk_size = 8192;
+const samplecnt_t Session::bounce_chunk_size = 8192;
static void clean_up_session_event (SessionEvent* ev) { delete ev; }
const SessionEvent::RTeventCallback Session::rt_cleanup (clean_up_session_event);
, process_function (&Session::process_with_events)
, _bounce_processing_active (false)
, waiting_for_sync_offset (false)
- , _base_frame_rate (0)
- , _nominal_frame_rate (0)
- , _current_frame_rate (0)
+ , _base_sample_rate (0)
+ , _nominal_sample_rate (0)
+ , _current_sample_rate (0)
, transport_sub_state (0)
, _record_status (Disabled)
- , _transport_frame (0)
+ , _transport_sample (0)
+ , _seek_counter (0)
, _session_range_location (0)
, _session_range_end_is_free (true)
, _slave (0)
, _silent (false)
+ , _remaining_latency_preroll (0)
+ , _engine_speed (1.0)
, _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)
- , maximum_output_latency (0)
- , _requested_return_frame (-1)
+ , _last_slave_transport_sample (0)
+ , _requested_return_sample (-1)
, current_block_size (0)
, _worst_output_latency (0)
, _worst_input_latency (0)
- , _worst_track_latency (0)
+ , _worst_route_latency (0)
+ , _send_latency_changes (0)
, _have_captured (false)
, _non_soloed_outs_muted (false)
, _listening (false)
, _realtime_export (false)
, _region_export (false)
, _export_preroll (0)
- , _export_latency (0)
, _pre_export_mmc_enabled (false)
, _name (snapshot_name)
, _is_new (true)
, _last_roll_or_reversal_location (0)
, _last_record_location (0)
, pending_locate_roll (false)
- , pending_locate_frame (0)
+ , pending_locate_sample (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))
, _bundle_xml_node (0)
, _current_trans (0)
, _clicking (false)
+ , _click_rec_only (false)
, click_data (0)
, click_emphasis_data (0)
, click_length (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)
, _midi_ports (0)
, _mmc (0)
, _vca_manager (new VCAManager (*this))
+ , _selection (new CoreSelection (*this))
+ , _global_locate_pending (false)
{
uint32_t sr = 0;
// set samplerate for plugins added early
// e.g from templates or MB channelstrip
set_block_size (_engine.samples_per_cycle());
- set_frame_rate (_engine.sample_rate());
+ set_sample_rate (_engine.sample_rate());
if (create (mix_template, bus_profile)) {
destroy ();
*/
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);
}
}
}
- 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);
_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));
StartTimeChanged.connect_same_thread (*this, boost::bind (&Session::start_time_changed, this, _1));
EndTimeChanged.connect_same_thread (*this, boost::bind (&Session::end_time_changed, this, _1));
+ Send::ChangedLatency.connect_same_thread (*this, boost::bind (&Session::send_latency_compensation_change, this));
+
emit_thread_start ();
auto_connect_thread_start ();
ensure_subdirs (); // archived or zipped sessions may lack peaks/ analysis/ etc
- _is_new = false;
+ 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"));
}
* session or set state for an existing one.
*/
+ _rt_tasklist.reset (new RTTaskList ());
+
if (how_many_dsp_threads () > 1) {
/* For now, only create the graph if we are using >1 DSP threads, as
it is a bit slower than the old code with 1 thread.
}
if (config.get_jack_time_master()) {
- _engine.transport_locate (_transport_frame);
+ _engine.transport_locate (_transport_sample);
}
try {
- LocaleGuard lg;
BootMessage (_("Set up LTC"));
setup_ltc ();
BootMessage (_("Set up Click"));
_state_of_the_state = StateOfTheState (CannotSave|Deletion);
+ {
+ Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
+ ltc_tx_cleanup();
+ delete _slave;
+ _slave = 0;
+ }
+
/* disconnect from any and all signals that we are connected to */
Port::PortSignalDrop (); /* EMIT SIGNAL */
drop_connections ();
/* shutdown control surface protocols while we still have ports
- and the engine to move data to any devices.
- */
+ * and the engine to move data to any devices.
+ */
+
+ /* remove I/O objects before unsetting the engine session */
+ _click_io.reset ();
+ _ltc_input.reset ();
+ _ltc_output.reset ();
ControlProtocolManager::instance().drop_protocols ();
- /* stop autoconnecting */
+ /* stop auto dis/connecting */
auto_connect_thread_terminate ();
MIDI::Name::MidiPatchManager::instance().remove_search_path(session_directory().midi_patch_path());
EngineStateController::instance()->remove_session();
#endif
+ /* drop slave, if any. We don't use use_sync_source (0) because
+ * there's no reason to do all the other stuff that may happen
+ * when calling that method.
+ */
+ delete _slave;
+
/* deregister all ports - there will be no process or any other
* callbacks from the engine any more.
*/
Port::PortDrop (); /* EMIT SIGNAL */
- ltc_tx_cleanup();
-
/* clear history so that no references to objects are held any more */
_history.clear ();
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;
clear_clicks ();
/* need to remove auditioner before monitoring section
- * otherwise it is re-connected */
+ * otherwise it is re-connected.
+ * Note: If a session was never successfully loaded, there
+ * may not yet be an auditioner.
+ */
+ if (auditioner) {
+ auditioner->drop_references ();
+ }
auditioner.reset ();
/* drop references to routes held by the monitoring section
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 ();
case SessionEvent::Skip:
case SessionEvent::PunchIn:
case SessionEvent::PunchOut:
- case SessionEvent::RecordStart:
case SessionEvent::StopOnce:
case SessionEvent::RangeStop:
case SessionEvent::RangeLocate:
}
}
+ {
+ /* 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 ();
}
}
+int
+Session::add_master_bus (ChanCount const& count)
+{
+ if (master_out ()) {
+ return -1;
+ }
+
+ RouteList rl;
+
+ boost::shared_ptr<Route> 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 ()
{
if (a->init()) {
throw failed_constructor ();
}
- a->use_new_diskstream ();
auditioner = a;
}
{
replace_event (SessionEvent::PunchIn, location->start());
- if (get_record_enabled() && config.get_punch_in()) {
+ if (get_record_enabled() && config.get_punch_in() && !actively_recording ()) {
/* capture start has been changed, so save new pending state */
save_state ("", true);
}
void
Session::auto_punch_end_changed (Location* location)
{
- framepos_t when_to_stop = location->end();
- // when_to_stop += _worst_output_latency + _worst_input_latency;
- replace_event (SessionEvent::PunchOut, when_to_stop);
+ replace_event (SessionEvent::PunchOut, location->end());
}
void
Session::auto_punch_changed (Location* location)
{
- framepos_t when_to_stop = location->end();
-
- replace_event (SessionEvent::PunchIn, location->start());
- //when_to_stop += _worst_output_latency + _worst_input_latency;
- replace_event (SessionEvent::PunchOut, when_to_stop);
+ auto_punch_start_changed (location);
+ auto_punch_end_changed (location);
}
/** @param loc A loop location.
- * @param pos Filled in with the start time of the required fade-out (in session frames).
+ * @param pos Filled in with the start time of the required fade-out (in session samples).
* @param length Filled in with the length of the required fade-out.
*/
void
-Session::auto_loop_declick_range (Location* loc, framepos_t & pos, framepos_t & length)
+Session::auto_loop_declick_range (Location* loc, samplepos_t & pos, samplepos_t & length)
{
pos = max (loc->start(), loc->end() - 64);
length = loc->end() - pos;
Session::auto_loop_changed (Location* location)
{
replace_event (SessionEvent::AutoLoop, location->end(), location->start());
- framepos_t dcp;
- framecnt_t dcl;
+ samplepos_t dcp;
+ samplecnt_t dcl;
auto_loop_declick_range (location, dcp, dcl);
if (transport_rolling() && play_loop) {
replace_event (SessionEvent::AutoLoopDeclick, dcp, dcl);
- // if (_transport_frame > location->end()) {
+ // if (_transport_sample > location->end()) {
- if (_transport_frame < location->start() || _transport_frame > location->end()) {
+ if (_transport_sample < location->start() || _transport_sample > location->end()) {
// relocate to beginning of loop
clear_events (SessionEvent::LocateRoll);
}
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;
to the loop start on stop if that is appropriate.
*/
- framepos_t pos;
+ samplepos_t pos;
if (!transport_rolling() && select_playhead_priority_target (pos)) {
if (pos == location->start()) {
if ((existing = _locations->auto_punch_location()) != 0 && existing != location) {
punch_connections.drop_connections();
existing->set_auto_punch (false, this);
- remove_event (existing->start(), SessionEvent::PunchIn);
+ clear_events (SessionEvent::PunchIn);
clear_events (SessionEvent::PunchOut);
auto_punch_location_changed (0);
}
}
void
-Session::set_session_extents (framepos_t start, framepos_t end)
+Session::set_session_extents (samplepos_t start, samplepos_t end)
{
Location* existing;
if ((existing = _locations->session_range_location()) == 0) {
loop_connections.drop_connections ();
existing->set_auto_loop (false, this);
remove_event (existing->end(), SessionEvent::AutoLoop);
- framepos_t dcp;
- framecnt_t dcl;
+ samplepos_t dcp;
+ samplecnt_t dcl;
auto_loop_declick_range (existing, dcp, dcl);
remove_event (dcp, SessionEvent::AutoLoopDeclick);
auto_loop_location_changed (0);
boost::shared_ptr<RouteList> rl = routes.reader ();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (tr && !tr->hidden()) {
+ if (tr && !tr->is_private_route()) {
tr->set_loop (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()) {
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 (g_atomic_int_compare_and_exchange (&_record_status, rs, Recording)) {
- _last_record_location = _transport_frame;
+ _last_record_location = _transport_sample;
send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe));
if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
if (!rt_context) {
remove_pending_capture_state ();
}
- unset_preroll_record_punch ();
}
}
}
if (_transport_speed) {
- if (!config.get_punch_in() && !preroll_record_punch_enabled ()) {
+ if (!config.get_punch_in()) {
enable_record ();
}
} else {
set_dirty();
}
-framepos_t
-Session::audible_frame () const
+samplepos_t
+Session::audible_sample (bool* latent_locate) const
{
- framepos_t ret;
+ if (latent_locate) {
+ *latent_locate = false;
+ }
- frameoffset_t offset = worst_playback_latency (); // - _engine.samples_since_cycle_start ();
- offset *= transport_speed ();
+ samplepos_t ret;
if (synced_to_engine()) {
/* Note: this is basically just sync-to-JACK */
- ret = _engine.transport_frame();
+ ret = _engine.transport_sample();
} else {
- ret = _transport_frame;
+ ret = _transport_sample;
}
- if (transport_rolling()) {
- ret -= offset;
+ assert (ret >= 0);
- /* 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.
- */
-
- if (_transport_speed > 0.0f) {
+ if (!transport_rolling()) {
+ return ret;
+ }
- if (!play_loop || !have_looped) {
- 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;
+#if 0 // TODO looping
+ if (_transport_speed > 0.0f) {
+ if (play_loop && have_looped) {
+ /* 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();
+ sampleoffset_t lo = location->start() - ret;
+ if (lo > 0) {
+ ret = location->end () - lo;
+ if (latent_locate) {
+ *latent_locate = true;
}
}
-
- } else if (_transport_speed < 0.0f) {
-
- /* XXX wot? no backward looping? */
-
- if (ret > _last_roll_or_reversal_location) {
- return _last_roll_or_reversal_location;
- }
}
+ } else if (_transport_speed < 0.0f) {
+ /* XXX wot? no backward looping? */
}
+#endif
- return std::max ((framepos_t)0, ret);
+ return std::max ((samplepos_t)0, ret);
}
-
-framecnt_t
-Session::preroll_samples (framepos_t pos) const
+samplecnt_t
+Session::preroll_samples (samplepos_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;
+ const Tempo& tempo = _tempo_map->tempo_at_sample (pos);
+ const Meter& meter = _tempo_map->meter_at_sample (pos);
+ return meter.samples_per_bar (tempo, sample_rate()) * -pr;
}
if (pr < 0) {
return 0;
}
- return pr * frame_rate();
+ return pr * sample_rate();
}
void
-Session::set_frame_rate (framecnt_t frames_per_second)
+Session::set_sample_rate (samplecnt_t frames_per_second)
{
- /** \fn void Session::set_frame_size(framecnt_t)
+ /** \fn void Session::set_sample_size(samplecnt_t)
the AudioEngine object that calls this guarantees
that it will not be called while we are also in
::process(). Its fine to do things that block
here.
*/
- if (_base_frame_rate == 0) {
- _base_frame_rate = frames_per_second;
+ if (_base_sample_rate == 0) {
+ _base_sample_rate = frames_per_second;
}
- else if (_base_frame_rate != frames_per_second && frames_per_second != _nominal_frame_rate) {
- NotifyAboutSampleRateMismatch (_base_frame_rate, frames_per_second);
+ else if (_base_sample_rate != frames_per_second && frames_per_second != _nominal_sample_rate) {
+ NotifyAboutSampleRateMismatch (_base_sample_rate, frames_per_second);
}
- _nominal_frame_rate = frames_per_second;
+ _nominal_sample_rate = frames_per_second;
sync_time_vars();
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (tr && !tr->is_auditioner()) {
- in += tr->n_inputs();
- out += tr->n_outputs();
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+ if (!tr) {
+ continue;
}
+ assert (!tr->is_auditioner()); // XXX remove me
+ in += tr->n_inputs();
+ out += tr->n_outputs();
}
}
track->set_strict_io (true);
}
- track->use_new_diskstream();
-
BOOST_MARK_TRACK (track);
{
}
}
- 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);
}
#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<Stripable> 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;
for (StripableList::iterator si = sl.begin(); si != sl.end(); ++si) {
boost::shared_ptr<Stripable> s (*si);
- if (s->is_monitor() || s->is_auditioner()) {
+ if (s->presentation_info().special (false)) {
+ continue;
+ }
+
+ if (!s->presentation_info().order_set()) {
continue;
}
}
}
- track->use_new_diskstream();
-
BOOST_MARK_TRACK (track);
{
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);
}
}
/* set this name in the XML description that we are about to use */
-
+#warning fixme -- no more Diskstream
if (pd == CopyPlaylist) {
XMLNode* ds_node = find_named_node (node_copy, "Diskstream");
if (ds_node) {
*/
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<Route> r = route_by_id (target->value());
if (!r || boost::dynamic_pointer_cast<Track>(r)) {
- (*i)->add_property ("type", "dangling-aux-send");
+ (*i)->set_property ("type", "dangling-aux-send");
continue;
}
}
(*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
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> route (XMLRouteFactory (node_copy, 3000));
if (route == 0) {
graph_reordered ();
- update_latency (true);
update_latency (false);
+ update_latency (true);
set_dirty();
{
RCUWriter<RouteList> writer (routes);
boost::shared_ptr<RouteList> 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
}
}
- 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<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)));
- 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->is_monitor()) {
+ _monitor_out = r;
}
- }
- if (!r->presentation_info().special()) {
+ 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)));
+ }
+ }
- 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) {
void
Session::remove_routes (boost::shared_ptr<RouteList> routes_to_remove)
{
+ bool mute_changed = false;
+ bool send_selected = false;
+
{ // RCU Writer scope
PBD::Unwinder<bool> uw_flag (_route_deletion_in_progress, true);
RCUWriter<RouteList> writer (routes);
boost::shared_ptr<RouteList> 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);
}
+ if ((*iter)->mute_control()->muted ()) {
+ mute_changed = true;
+ }
+
rs->remove (*iter);
/* deleting the master out seems like a dumb
} // end of RCU Writer scope
+ if (mute_changed) {
+ MuteChanged (); /* EMIT SIGNAL */
+ }
+
update_route_solo_state ();
update_latency_compensation ();
set_dirty();
routes.flush ();
+ /* remove these routes from the selection if appropriate, and signal
+ * the change *before* we call DropReferences for them.
+ */
+
+ if (send_selected && !deletion_in_progress()) {
+ for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) {
+ _selection->remove_stripable_by_id ((*iter)->id());
+ }
+ PropertyChange pc;
+ pc.add (Properties::selected);
+ PresentationInfo::Change (pc);
+ }
+
/* try to cause everyone to drop their references
* and unregister ports from the backend
*/
for (RouteList::iterator iter = routes_to_remove->begin(); iter != routes_to_remove->end(); ++iter) {
- cerr << "Drop references to " << (*iter)->name() << endl;
(*iter)->drop_references ();
}
- if (_state_of_the_state & Deletion) {
+ if (deletion_in_progress()) {
return;
}
- PresentationInfo::Change(); /* EMIT SIGNAL */
+ PropertyChange pc;
+ pc.add (Properties::order);
+ PresentationInfo::Change (pc);
/* save the new state of the world */
void
Session::route_mute_changed ()
{
+ MuteChanged (); /* EMIT SIGNAL */
set_dirty ();
}
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<Route> r = boost::dynamic_pointer_cast<Route>(*i);
+ if (r && !r->active()) {
+ continue;
+ }
+ boost::shared_ptr<MuteControl> mc = (*i)->mute_control();
+ if (mc && mc->muted ()) {
+ muted = true;
+ break;
+ }
+ }
+ return muted;
+}
+
+std::vector<boost::weak_ptr<AutomationControl> >
+Session::cancel_all_mute ()
+{
+ StripableList all;
+ get_stripables (all);
+ std::vector<boost::weak_ptr<AutomationControl> > muted;
+ boost::shared_ptr<ControlList> 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<Route> r = boost::dynamic_pointer_cast<Route> (*i);
+ if (r && !r->active()) {
+ continue;
+ }
+ boost::shared_ptr<AutomationControl> ac = (*i)->mute_control();
+ if (ac && ac->get_value () > 0) {
+ cl->push_back (ac);
+ muted.push_back (boost::weak_ptr<AutomationControl>(ac));
+ }
+ }
+ if (!cl->empty ()) {
+ set_controls (cl, 0.0, PBD::Controllable::UseGroup);
+ }
+ return muted;
+}
+
void
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<RouteList>
Session::get_routes_with_internal_returns() const
{
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;
+ /* 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;
return boost::shared_ptr<Route> ((Route*) 0);
}
-boost::shared_ptr<Processor>
-Session::processor_by_id (PBD::ID id) const
+
+boost::shared_ptr<Stripable>
+Session::stripable_by_id (PBD::ID id) const
{
- boost::shared_ptr<RouteList> r = routes.reader ();
+ StripableList sl;
+ get_stripables (sl);
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- boost::shared_ptr<Processor> 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<Processor> ();
+ return boost::shared_ptr<Stripable>();
}
-boost::shared_ptr<Track>
-Session::track_by_diskstream_id (PBD::ID id) const
+boost::shared_ptr<Processor>
+Session::processor_by_id (PBD::ID id) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (*i);
- if (t && t->using_diskstream_id (id)) {
- return t;
+ boost::shared_ptr<Processor> p = (*i)->Route::processor_by_id (id);
+ if (p) {
+ return p;
}
}
- return boost::shared_ptr<Track> ();
+ return boost::shared_ptr<Processor> ();
}
boost::shared_ptr<Route>
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) {
return boost::shared_ptr<Stripable>();
}
-struct PresentationOrderSorter {
- bool operator() (boost::shared_ptr<Stripable> a, boost::shared_ptr<Stripable> 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();
- }
- }
-};
-
boost::shared_ptr<Route>
Session::route_by_selected_count (uint32_t id) const
{
RouteList r (*(routes.reader ()));
- PresentationOrderSorter sorter;
- r.sort (sorter);
+ r.sort (Stripable::Sorter());
RouteList::iterator i;
for (i = r.begin(); i != r.end(); ++i) {
- if ((*i)->presentation_info().selected()) {
+ if ((*i)->is_selected()) {
if (id == 0) {
return *i;
}
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<Track> (*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);
}
}
/* If so, update the session range markers */
if (!in.empty ()) {
- maybe_update_session_range (r->position (), r->last_frame ());
+ maybe_update_session_range (r->position (), r->last_sample ());
}
}
* b is after the current end.
*/
void
-Session::maybe_update_session_range (framepos_t a, framepos_t b)
+Session::maybe_update_session_range (samplepos_t a, samplepos_t b)
{
if (_state_of_the_state & Loading) {
return;
}
- framepos_t session_end_marker_shift_samples = session_end_shift * _nominal_frame_rate;
+ samplepos_t session_end_marker_shift_samples = session_end_shift * _nominal_sample_rate;
if (_session_range_location == 0) {
}
void
-Session::playlist_ranges_moved (list<Evoral::RangeMove<framepos_t> > const & ranges)
+Session::playlist_ranges_moved (list<Evoral::RangeMove<samplepos_t> > const & ranges)
{
- for (list<Evoral::RangeMove<framepos_t> >::const_iterator i = ranges.begin(); i != ranges.end(); ++i) {
+ for (list<Evoral::RangeMove<samplepos_t> >::const_iterator i = ranges.begin(); i != ranges.end(); ++i) {
maybe_update_session_range (i->to, i->to + i->length);
}
}
void
-Session::playlist_regions_extended (list<Evoral::Range<framepos_t> > const & ranges)
+Session::playlist_regions_extended (list<Evoral::Range<samplepos_t> > const & ranges)
{
- for (list<Evoral::Range<framepos_t> >::const_iterator i = ranges.begin(); i != ranges.end(); ++i) {
+ for (list<Evoral::Range<samplepos_t> >::const_iterator i = ranges.begin(); i != ranges.end(); ++i) {
maybe_update_session_range (i->from, i->to);
}
}
}
boost::shared_ptr<MidiSource>
-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<MidiSource> ms
/** 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<string> sdirs = source_search_path(DataType::MIDI);
*/
std::reverse(sdirs.begin(), sdirs.end());
- for (cnt = 1; cnt <= limit; ++cnt) {
+ while (true) {
+ possible_name = bump_name_once (possible_name, '-');
vector<space_and_path>::iterator i;
uint32_t existing = 0;
for (vector<string>::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
if (!path.empty()) {
return boost::dynamic_pointer_cast<AudioFileSource> (
- SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate(), true, true));
+ SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, sample_rate(), true, true));
} else {
throw failed_constructor ();
}
if (!path.empty()) {
return boost::dynamic_pointer_cast<SMFSource> (
SourceFactory::createWritable (
- DataType::MIDI, *this, path, false, frame_rate()));
+ DataType::MIDI, *this, path, false, sample_rate()));
} else {
throw failed_constructor ();
}
return boost::dynamic_pointer_cast<SMFSource> (
SourceFactory::createWritable (
- DataType::MIDI, *this, path, false, frame_rate()));
+ DataType::MIDI, *this, path, false, sample_rate()));
}
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();
}
if (!i.key ().isString ()) { assert(0); continue; }
rv.push_back (i.key ().cast<std::string> ());
}
- } catch (luabridge::LuaException const& e) { }
+ } catch (...) { }
return rv;
}
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) { }
- lua.collect_garbage_step ();
+ try { (*_lua_run)(nframes); } catch (...) { }
+ lua.collect_garbage_step (100 /*kB*/);
}
}
lua.Print.connect (&_lua_print);
#endif
lua.tweak_rt_gc ();
+ lua.sandbox (true);
lua.do_command (
"function ArdourSession ()"
" local self = { scripts = {}, instances = {} }"
" 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, 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
_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*/
}
}
_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;
}
}
-bool
-Session::RoutePublicOrderSorter::operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> 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
{
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
- reflect any changes in latencies within the graph.
- */
-
- boost::shared_ptr<RouteList> rl = routes.reader ();
- for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (tr) {
- tr->set_capture_offset ();
- }
- }
+ * reflect any changes in latencies within the graph.
+ */
+ update_route_latency (false, true);
}
-/** @return Number of frames that there is disk space available to write,
+/** @return Number of samples that there is disk space available to write,
* if known.
*/
-boost::optional<framecnt_t>
+boost::optional<samplecnt_t>
Session::available_capture_duration ()
{
Glib::Threads::Mutex::Lock lm (space_lock);
if (_total_free_4k_blocks_uncertain) {
- return boost::optional<framecnt_t> ();
+ return boost::optional<samplecnt_t> ();
}
float sample_bytes_on_disk = 4.0; // keep gcc happy
double scale = 4096.0 / sample_bytes_on_disk;
- if (_total_free_4k_blocks * scale > (double) max_framecnt) {
- return max_framecnt;
+ if (_total_free_4k_blocks * scale > (double) max_samplecnt) {
+ return max_samplecnt;
}
- return (framecnt_t) floor (_total_free_4k_blocks * scale);
+ return (samplecnt_t) floor (_total_free_4k_blocks * scale);
}
void
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_beat ();
+ (*i)->recompute_samples_from_beat ();
}
}
}
boost::shared_ptr<Region>
-Session::write_one_track (Track& track, framepos_t start, framepos_t end,
+Session::write_one_track (Track& track, samplepos_t start, samplepos_t end,
bool /*overwrite*/, vector<boost::shared_ptr<Source> >& srcs,
InterThreadInfo& itt,
boost::shared_ptr<Processor> endpoint, bool include_endpoint,
boost::shared_ptr<Playlist> playlist;
boost::shared_ptr<Source> source;
ChanCount diskstream_channels (track.n_channels());
- framepos_t position;
- framecnt_t this_chunk;
- framepos_t to_do;
- framepos_t latency_skip;
+ samplepos_t position;
+ samplecnt_t this_chunk;
+ samplepos_t to_do;
+ samplepos_t latency_skip;
BufferSet buffers;
- framepos_t len = end - start;
+ samplepos_t len = end - start;
bool need_block_size_reset = false;
ChanCount const max_proc = track.max_processor_streams ();
string legal_playlist_name;
}
try {
- source = SourceFactory::createWritable (track.data_type(), *this, path, false, frame_rate());
+ source = SourceFactory::createWritable (track.data_type(), *this, path, false, sample_rate());
}
catch (failed_constructor& err) {
continue;
}
- const framecnt_t current_chunk = this_chunk - latency_skip;
+ const samplecnt_t current_chunk = this_chunk - latency_skip;
uint32_t n = 0;
for (vector<boost::shared_ptr<Source> >::iterator src=srcs.begin(); src != srcs.end(); ++src, ++n) {
const MidiBuffer& buf = buffers.get_midi(0);
for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
- Evoral::Event<framepos_t> ev = *i;
+ Evoral::Event<samplepos_t> ev = *i;
ev.set_time(ev.time() - position);
- ms->append_event_frames(lock, ev, ms->timeline_position());
+ ms->append_event_samples(lock, ev, ms->timeline_position());
}
}
}
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
{
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)
{
for (RouteList::const_iterator r = rl->begin(); r != rl->end(); ++r) {
if (boost::dynamic_pointer_cast<Track> (*r)) {
- if (!(*r)->is_auditioner()) {
- tl->push_back (*r);
- }
+ assert (!(*r)->is_auditioner()); // XXX remove me
+ tl->push_back (*r);
}
}
return tl;
}
boost::shared_ptr<RouteList>
-Session::get_routes_with_regions_at (framepos_t const p) const
+Session::get_routes_with_regions_at (samplepos_t const p) const
{
boost::shared_ptr<RouteList> r = routes.reader ();
boost::shared_ptr<RouteList> rl (new RouteList);
}
}
-framepos_t
-Session::current_start_frame () const
+samplepos_t
+Session::current_start_sample () const
{
return _session_range_location ? _session_range_location->start() : 0;
}
-framepos_t
-Session::current_end_frame () const
+samplepos_t
+Session::current_end_sample () const
{
return _session_range_location ? _session_range_location->end() : 0;
}
void
-Session::set_session_range_location (framepos_t start, framepos_t end)
+Session::set_session_range_location (samplepos_t start, samplepos_t end)
{
_session_range_location = new Location (*this, start, end, _("session"), Location::IsSessionRange, 0);
_locations->add (_session_range_location);
void
-Session::start_time_changed (framepos_t old)
+Session::start_time_changed (samplepos_t old)
{
/* Update the auto loop range to match the session range
(unless the auto loop range has been changed by the user)
}
void
-Session::end_time_changed (framepos_t old)
+Session::end_time_changed (samplepos_t old)
{
/* Update the auto loop range to match the session range
(unless the auto loop range has been changed by the user)
}
void
-Session::update_latency (bool playback)
+Session::set_worst_io_latencies_x (IOChange, void *)
{
+ set_worst_io_latencies ();
+}
- DEBUG_TRACE (DEBUG::Latency, string_compose ("JACK latency callback: %1\n", (playback ? "PLAYBACK" : "CAPTURE")));
-
- if ((_state_of_the_state & (InitialConnecting|Deletion)) || _adding_routes_in_progress || _route_deletion_in_progress) {
- return;
- }
+void
+Session::send_latency_compensation_change ()
+{
+ /* As a result of Send::set_output_latency()
+ * or InternalReturn::set_playback_offset ()
+ * the send's own latency can change (source track
+ * is aligned with target bus).
+ *
+ * This can only happen be triggered by
+ * Route::update_signal_latency ()
+ * when updating the processor latency.
+ *
+ * We need to walk the graph again to take those changes into account
+ * (we should probably recurse or process the graph in a 2 step process).
+ */
+ ++_send_latency_changes;
+}
+bool
+Session::update_route_latency (bool playback, bool apply_to_delayline)
+{
+ /* Note: RouteList is process-graph sorted */
boost::shared_ptr<RouteList> r = routes.reader ();
- framecnt_t max_latency = 0;
if (playback) {
- /* reverse the list so that we work backwards from the last route to run to the first */
- RouteList* rl = routes.reader().get();
- r.reset (new RouteList (*rl));
+ /* reverse the list so that we work backwards from the last route to run to the first,
+ * this is not needed, but can help to reduce the iterations for aux-sends.
+ */
+ RouteList* rl = routes.reader().get();
+ r.reset (new RouteList (*rl));
reverse (r->begin(), r->end());
}
- /* compute actual latency values for the given direction and store them all in per-port
- structures. this will also publish the same values (to JACK) so that computation of latency
- for routes can consistently use public latency values.
- */
+ bool changed = false;
+ int bailout = 0;
+restart:
+ _send_latency_changes = 0;
+ _worst_route_latency = 0;
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- max_latency = max (max_latency, (*i)->set_private_port_latencies (playback));
+ // if (!(*i)->active()) { continue ; } // TODO
+ samplecnt_t l;
+ if ((*i)->signal_latency () != (l = (*i)->update_signal_latency (apply_to_delayline))) {
+ changed = true;
+ }
+ _worst_route_latency = std::max (l, _worst_route_latency);
}
- /* because we latency compensate playback, our published playback latencies should
- be the same for all output ports - all material played back by ardour has
- the same latency, whether its caused by plugins or by latency compensation. since
- these may differ from the values computed above, reset all playback port latencies
- to the same value.
- */
-
- DEBUG_TRACE (DEBUG::Latency, string_compose ("Set public port latencies to %1\n", max_latency));
-
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- (*i)->set_public_port_latencies (max_latency, playback);
- }
-
- if (playback) {
-
- post_playback_latency ();
-
- } else {
-
- post_capture_latency ();
+ if (_send_latency_changes > 0) {
+ // only 1 extra iteration is needed (we allow only 1 level of aux-sends)
+ // BUT.. jack'n'sends'n'bugs
+ if (++bailout < 5) {
+ cerr << "restarting Session::update_latency. # of send changes: " << _send_latency_changes << " iteration: " << bailout << endl;
+ goto restart;
+ }
}
- DEBUG_TRACE (DEBUG::Latency, "JACK latency callback: DONE\n");
+ DEBUG_TRACE (DEBUG::Latency, string_compose ("worst signal processing latency: %1 (changed ? %2)\n", _worst_route_latency, (changed ? "yes" : "no")));
+
+ return changed;
}
void
-Session::post_playback_latency ()
+Session::update_latency (bool playback)
{
- set_worst_playback_latency ();
+ DEBUG_TRACE (DEBUG::Latency, string_compose ("JACK latency callback: %1\n", (playback ? "PLAYBACK" : "CAPTURE")));
+ if ((_state_of_the_state & (InitialConnecting|Deletion)) || _adding_routes_in_progress || _route_deletion_in_progress) {
+ return;
+ }
+ if (!_engine.running()) {
+ return;
+ }
+
+ /* Note; RouteList is sorted as process-graph */
boost::shared_ptr<RouteList> r = routes.reader ();
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- if (!(*i)->is_auditioner() && ((*i)->active())) {
- _worst_track_latency = max (_worst_track_latency, (*i)->update_signal_latency ());
- }
+ if (playback) {
+ /* reverse the list so that we work backwards from the last route to run to the first */
+ RouteList* rl = routes.reader().get();
+ r.reset (new RouteList (*rl));
+ reverse (r->begin(), r->end());
}
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- (*i)->set_latency_compensation (_worst_track_latency);
+ samplecnt_t latency = (*i)->set_private_port_latencies (playback);
+ (*i)->set_public_port_latencies (latency, playback);
}
-}
-
-void
-Session::post_capture_latency ()
-{
- set_worst_capture_latency ();
- /* reflect any changes in capture latencies into capture offsets
- */
-
- boost::shared_ptr<RouteList> rl = routes.reader();
- for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (tr) {
- tr->set_capture_offset ();
- }
+ if (playback) {
+ set_worst_output_latency ();
+ update_route_latency (true, true);
+ } else {
+ set_worst_input_latency ();
+ update_route_latency (false, false);
}
+
+ DEBUG_TRACE (DEBUG::Latency, "JACK latency callback: DONE\n");
}
void
Session::initialize_latencies ()
{
- {
- Glib::Threads::Mutex::Lock lm (_engine.process_lock());
- update_latency (false);
- update_latency (true);
- }
+ {
+ Glib::Threads::Mutex::Lock lm (_engine.process_lock());
+ update_latency (false);
+ update_latency (true);
+ }
- set_worst_io_latencies ();
+ set_worst_io_latencies ();
}
void
Session::set_worst_io_latencies ()
{
- set_worst_playback_latency ();
- set_worst_capture_latency ();
+ set_worst_output_latency ();
+ set_worst_input_latency ();
}
void
-Session::set_worst_playback_latency ()
+Session::set_worst_output_latency ()
{
if (_state_of_the_state & (InitialConnecting|Deletion)) {
return;
_worst_output_latency = max (_worst_output_latency, (*i)->output()->latency());
}
+ _worst_output_latency = max (_worst_output_latency, _click_io->latency());
+
DEBUG_TRACE (DEBUG::Latency, string_compose ("Worst output latency: %1\n", _worst_output_latency));
}
void
-Session::set_worst_capture_latency ()
+Session::set_worst_input_latency ()
{
if (_state_of_the_state & (InitialConnecting|Deletion)) {
return;
_worst_input_latency = max (_worst_input_latency, (*i)->input()->latency());
}
- DEBUG_TRACE (DEBUG::Latency, string_compose ("Worst input latency: %1\n", _worst_input_latency));
+ DEBUG_TRACE (DEBUG::Latency, string_compose ("Worst input latency: %1\n", _worst_input_latency));
}
void
Session::update_latency_compensation (bool force_whole_graph)
{
- bool some_track_latency_changed = false;
-
if (_state_of_the_state & (InitialConnecting|Deletion)) {
return;
}
- DEBUG_TRACE(DEBUG::Latency, "---------------------------- update latency compensation\n\n");
-
- _worst_track_latency = 0;
-
- boost::shared_ptr<RouteList> r = routes.reader ();
-
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- if (!(*i)->is_auditioner() && ((*i)->active())) {
- framecnt_t tl;
- if ((*i)->signal_latency () != (tl = (*i)->update_signal_latency ())) {
- some_track_latency_changed = true;
- }
- _worst_track_latency = max (tl, _worst_track_latency);
- }
- }
-
- DEBUG_TRACE (DEBUG::Latency, string_compose ("worst signal processing latency: %1 (changed ? %2)\n", _worst_track_latency,
- (some_track_latency_changed ? "yes" : "no")));
-
- DEBUG_TRACE(DEBUG::Latency, "---------------------------- DONE update latency compensation\n\n");
+ bool some_track_latency_changed = update_route_latency (false, false);
if (some_track_latency_changed || force_whole_graph) {
_engine.update_latencies ();
- }
-
-
- for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (!tr) {
- continue;
+ /* above call will ask the backend up update its latencies, which
+ * eventually will trigger AudioEngine::latency_callback () and
+ * call Session::update_latency ()
+ */
+ } else {
+ boost::shared_ptr<RouteList> r = routes.reader ();
+ for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+ (*i)->apply_latency_compensation ();
}
- tr->set_capture_offset ();
}
}
return;
}
- PresentationInfo::Change (); /* EMIT SIGNAL */
reassign_track_numbers();
#ifdef USE_TRACKS_CODE_FEATURES
boost::shared_ptr<Port>
Session::ltc_input_port () const
{
+ assert (_ltc_input);
return _ltc_input->nth (0);
}
boost::shared_ptr<Port>
Session::ltc_output_port () const
{
- return _ltc_output->nth (0);
+ return _ltc_output ? _ltc_output->nth (0) : boost::shared_ptr<Port> ();
}
void
}
void
-Session::set_range_selection (framepos_t start, framepos_t end)
+Session::set_range_selection (samplepos_t start, samplepos_t end)
{
- _range_selection = Evoral::Range<framepos_t> (start, end);
+ _range_selection = Evoral::Range<samplepos_t> (start, end);
#ifdef USE_TRACKS_CODE_FEATURES
follow_playhead_priority ();
#endif
}
void
-Session::set_object_selection (framepos_t start, framepos_t end)
+Session::set_object_selection (samplepos_t start, samplepos_t end)
{
- _object_selection = Evoral::Range<framepos_t> (start, end);
+ _object_selection = Evoral::Range<samplepos_t> (start, end);
#ifdef USE_TRACKS_CODE_FEATURES
follow_playhead_priority ();
#endif
void
Session::clear_range_selection ()
{
- _range_selection = Evoral::Range<framepos_t> (-1,-1);
+ _range_selection = Evoral::Range<samplepos_t> (-1,-1);
#ifdef USE_TRACKS_CODE_FEATURES
follow_playhead_priority ();
#endif
void
Session::clear_object_selection ()
{
- _object_selection = Evoral::Range<framepos_t> (-1,-1);
+ _object_selection = Evoral::Range<samplepos_t> (-1,-1);
#ifdef USE_TRACKS_CODE_FEATURES
follow_playhead_priority ();
#endif
/* 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
- * modifies the capture offset... which can be a proplem
- * in "prepare_to_stop"
+ * update_latency_compensation ()
+ * calls DiskWriter::set_capture_offset () which
+ * modifies the capture-offset, which can be a problem.
*/
while (g_atomic_int_and (&_latency_recompute_pending, 0)) {
+ Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
update_latency_compensation ();
}
}