X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession_transport.cc;h=9163dfd5ea6f54eb2977410425a15ececcb218a8;hb=7a76e8ae96c20b270dd3104328f8bee199c9a770;hp=4a1f7a182bac0c722531ee4caaf4c94983fe9005;hpb=b09ab546542040b1d468c9925c60bda2dfd80da8;p=ardour.git diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 4a1f7a182b..ace5e8e3e1 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 1999-2003 Paul Davis + Copyright (C) 1999-2003 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,102 +15,152 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ +#ifdef WAF_BUILD +#include "libardour-config.h" +#endif + #include #include #include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include +#include "pbd/undo.h" +#include "pbd/error.h" +#include "pbd/enumwriter.h" +#include "pbd/pthread_utils.h" +#include "pbd/memento_command.h" + +#include "midi++/mmc.h" +#include "midi++/port.h" +#include "midi++/manager.h" + +#include "ardour/audioengine.h" +#include "ardour/auditioner.h" +#include "ardour/butler.h" +#include "ardour/click.h" +#include "ardour/debug.h" +#include "ardour/location.h" +#include "ardour/session.h" +#include "ardour/slave.h" +#include "ardour/operations.h" #include "i18n.h" using namespace std; using namespace ARDOUR; -using namespace sigc; +using namespace PBD; + +void +Session::add_post_transport_work (PostTransportWork ptw) +{ + PostTransportWork oldval; + PostTransportWork newval; + int tries = 0; + + while (tries < 8) { + oldval = (PostTransportWork) g_atomic_int_get (&_post_transport_work); + newval = PostTransportWork (oldval | ptw); + if (g_atomic_int_compare_and_exchange (&_post_transport_work, oldval, newval)) { + /* success */ + return; + } + } + + error << "Could not set post transport work! Crazy thread madness, call the programmers" << endmsg; +} void Session::request_input_change_handling () { - Event* ev = new Event (Event::InputConfigurationChange, Event::Add, Event::Immediate, 0, 0.0); - queue_event (ev); + if (!(_state_of_the_state & (InitialConnecting|Deletion))) { + SessionEvent* ev = new SessionEvent (SessionEvent::InputConfigurationChange, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0); + queue_event (ev); + } } void -Session::request_slave_source (SlaveSource src, jack_nframes_t pos) +Session::request_sync_source (Slave* new_slave) { - Event* ev = new Event (Event::SetSlaveSource, Event::Add, Event::Immediate, pos, 0.0); + SessionEvent* ev = new SessionEvent (SessionEvent::SetSyncSource, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0); + bool seamless; - if (src == Session::JACK) { - /* could set_seamless_loop() be disposed of entirely?*/ - set_seamless_loop (false); - } else { + seamless = Config->get_seamless_loop (); - set_seamless_loop (true); + if (dynamic_cast(new_slave)) { + /* JACK cannot support seamless looping at present */ + Config->set_seamless_loop (false); + } else { + /* reset to whatever the value was before we last switched slaves */ + Config->set_seamless_loop (_was_seamless); } - ev->slave = src; + + /* save value of seamless from before the switch */ + _was_seamless = seamless; + + ev->slave = new_slave; queue_event (ev); } void -Session::request_transport_speed (float speed) +Session::request_transport_speed (double speed) { - Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, speed); + SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, speed); + DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport speed = %1\n", speed)); queue_event (ev); } +/** Request a new transport speed, but if the speed parameter is exactly zero then use + * a very small +ve value to prevent the transport actually stopping. This method should + * be used by callers who are varying transport speed but don't ever want to stop it. + */ +void +Session::request_transport_speed_nonzero (double speed) +{ + if (speed == 0) { + speed = DBL_EPSILON; + } + + request_transport_speed (speed); +} + void -Session::request_diskstream_speed (AudioDiskstream& ds, float speed) +Session::request_track_speed (Track* tr, double speed) { - Event* ev = new Event (Event::SetDiskstreamSpeed, Event::Add, Event::Immediate, 0, speed); - ev->set_ptr (&ds); + SessionEvent* ev = new SessionEvent (SessionEvent::SetTrackSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, speed); + ev->set_ptr (tr); queue_event (ev); } void -Session::request_stop (bool abort) +Session::request_stop (bool abort, bool clear_state) { - Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0, abort); + SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0, abort, clear_state); + DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport stop, abort = %1, clear state = %2\n", abort, clear_state)); queue_event (ev); } void -Session::request_locate (jack_nframes_t target_frame, bool with_roll) +Session::request_locate (framepos_t target_frame, bool with_roll) { - Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, false); + SessionEvent *ev = new SessionEvent (with_roll ? SessionEvent::LocateRoll : SessionEvent::Locate, SessionEvent::Add, SessionEvent::Immediate, target_frame, 0, false); + DEBUG_TRACE (DEBUG::Transport, string_compose ("Request locate to %1\n", target_frame)); queue_event (ev); } void -Session::force_locate (jack_nframes_t target_frame, bool with_roll) +Session::force_locate (framepos_t target_frame, bool with_roll) { - Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, true); + SessionEvent *ev = new SessionEvent (with_roll ? SessionEvent::LocateRoll : SessionEvent::Locate, SessionEvent::Add, SessionEvent::Immediate, target_frame, 0, true); + DEBUG_TRACE (DEBUG::Transport, string_compose ("Request forced locate to %1\n", target_frame)); queue_event (ev); } void -Session::request_auto_loop (bool yn) +Session::request_play_loop (bool yn, bool leave_rolling) { - Event* ev; - Location *location = _locations.auto_loop_location(); + SessionEvent* ev; + Location *location = _locations->auto_loop_location(); if (location == 0 && yn) { error << _("Cannot loop - no loop range defined") @@ -118,171 +168,294 @@ Session::request_auto_loop (bool yn) return; } - ev = new Event (Event::SetLoop, Event::Add, Event::Immediate, 0, 0.0, yn); + ev = new SessionEvent (SessionEvent::SetLoop, SessionEvent::Add, SessionEvent::Immediate, 0, (leave_rolling ? 1.0 : 0.0), yn); + DEBUG_TRACE (DEBUG::Transport, string_compose ("Request set loop = %1, leave rolling ? %2\n", yn, leave_rolling)); queue_event (ev); - if (!yn && seamless_loop && transport_rolling()) { - // request an immediate locate to refresh the diskstreams + if (!leave_rolling && !yn && Config->get_seamless_loop() && transport_rolling()) { + // request an immediate locate to refresh the tracks // after disabling looping - request_locate (_transport_frame-1, true); + request_locate (_transport_frame-1, false); } } void -Session::set_seamless_loop (bool yn) +Session::request_play_range (list* range, bool leave_rolling) { - if (seamless_loop != yn) { - seamless_loop = yn; - - if (auto_loop && transport_rolling()) { - // to reset diskstreams etc - request_auto_loop (true); - } - - ControlChanged (SeamlessLoop); /* EMIT */ + SessionEvent* ev = new SessionEvent (SessionEvent::SetPlayAudioRange, SessionEvent::Add, SessionEvent::Immediate, 0, (leave_rolling ? 1.0 : 0.0)); + if (range) { + ev->audio_range = *range; + } else { + ev->audio_range.clear (); } + DEBUG_TRACE (DEBUG::Transport, string_compose ("Request play range, leave rolling ? %1\n", leave_rolling)); + queue_event (ev); } void -Session::realtime_stop (bool abort) +Session::realtime_stop (bool abort, bool clear_state) { + DEBUG_TRACE (DEBUG::Transport, string_compose ("realtime stop @ %1\n", _transport_frame)); + PostTransportWork todo = PostTransportWork (0); + /* assume that when we start, we'll be moving forwards */ if (_transport_speed < 0.0f) { - post_transport_work = PostTransportWork (post_transport_work | PostTransportStop | PostTransportReverse); + todo = (PostTransportWork (todo | PostTransportStop | PostTransportReverse)); } else { - post_transport_work = PostTransportWork (post_transport_work | PostTransportStop); + todo = PostTransportWork (todo | PostTransportStop); + } + + /* call routes */ + + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin (); i != r->end(); ++i) { + (*i)->realtime_handle_transport_stopped (); } if (actively_recording()) { /* move the transport position back to where the request for a stop was noticed. we rolled - past that point to pick up delayed input. + past that point to pick up delayed input (and/or to delick) */ -#ifndef LEAVE_TRANSPORT_UNADJUSTED - decrement_transport_position (_worst_output_latency); -#endif + if (worst_playback_latency() > current_block_size) { + /* we rolled past the stop point to pick up data that had + not yet arrived. move back to where the stop occured. + */ + decrement_transport_position (current_block_size + (worst_input_latency() - current_block_size)); + } else { + decrement_transport_position (current_block_size); + } /* the duration change is not guaranteed to have happened, but is likely */ - post_transport_work = PostTransportWork (post_transport_work | PostTransportDuration); + todo = PostTransportWork (todo | PostTransportDuration); } if (abort) { - post_transport_work = PostTransportWork (post_transport_work | PostTransportAbort); + todo = PostTransportWork (todo | PostTransportAbort); } - _clear_event_type (Event::StopOnce); - _clear_event_type (Event::RangeStop); - _clear_event_type (Event::RangeLocate); + if (clear_state) { + todo = PostTransportWork (todo | PostTransportClearSubstate); + } + + if (todo) { + add_post_transport_work (todo); + } - disable_record (true); + _clear_event_type (SessionEvent::StopOnce); + _clear_event_type (SessionEvent::RangeStop); + _clear_event_type (SessionEvent::RangeLocate); + + /* if we're going to clear loop state, then force disabling record BUT only if we're not doing latched rec-enable */ + disable_record (true, (!Config->get_latched_record_enable() && clear_state)); reset_slave_state (); - + _transport_speed = 0; + _target_transport_speed = 0; + + g_atomic_int_set (&_playback_load, 100); + g_atomic_int_set (&_capture_load, 100); - transport_sub_state = (auto_return ? AutoReturning : 0); + if (config.get_use_video_sync()) { + waiting_for_sync_offset = true; + } + + transport_sub_state = 0; +} + +void +Session::realtime_locate () +{ + boost::shared_ptr r = routes.reader (); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->realtime_locate (); + } } void Session::butler_transport_work () { - Glib::RWLock::ReaderLock rm (route_lock); - Glib::RWLock::ReaderLock dsm (diskstream_lock); - - if (post_transport_work & PostTransportCurveRealloc) { - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { + restart: + bool finished; + PostTransportWork ptw; + boost::shared_ptr r = routes.reader (); + + int on_entry = g_atomic_int_get (&_butler->should_do_transport_work); + finished = true; + ptw = post_transport_work(); + + DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler transport work, todo = %1\n", enum_2_string (ptw))); + + if (ptw & PostTransportAdjustPlaybackBuffering) { + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->adjust_playback_buffering (); + /* and refill those buffers ... */ + tr->non_realtime_locate (_transport_frame); + } + } + + } + + if (ptw & PostTransportAdjustCaptureBuffering) { + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->adjust_capture_buffering (); + } + } + } + + if (ptw & PostTransportCurveRealloc) { + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { (*i)->curve_reallocate(); } } - if (post_transport_work & PostTransportInputChange) { - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - (*i)->non_realtime_input_change (); + if (ptw & PostTransportInputChange) { + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->non_realtime_input_change (); + } } } - if (post_transport_work & PostTransportSpeed) { + if (ptw & PostTransportSpeed) { non_realtime_set_speed (); } - if (post_transport_work & PostTransportReverse) { - + if (ptw & PostTransportReverse) { clear_clicks(); cumulative_rf_motion = 0; reset_rf_scale (0); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if (!(*i)->hidden()) { - if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) { - (*i)->seek ((jack_nframes_t) (_transport_frame * (double) (*i)->speed())); + /* don't seek if locate will take care of that in non_realtime_stop() */ + + if (!(ptw & PostTransportLocate)) { + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && !tr->hidden()) { + tr->non_realtime_locate (_transport_frame); } - else { - (*i)->seek (_transport_frame); + if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) { + /* new request, stop seeking, and start again */ + g_atomic_int_dec_and_test (&_butler->should_do_transport_work); + goto restart; } } } } - if (post_transport_work & (PostTransportStop|PostTransportLocate)) { - non_realtime_stop (post_transport_work & PostTransportAbort); + if (ptw & PostTransportLocate) { + non_realtime_locate (); } - if (post_transport_work & PostTransportOverWrite) { - non_realtime_overwrite (); + if (ptw & PostTransportStop) { + non_realtime_stop (ptw & PostTransportAbort, on_entry, finished); + if (!finished) { + g_atomic_int_dec_and_test (&_butler->should_do_transport_work); + goto restart; + } } - if (post_transport_work & PostTransportAudition) { + if (ptw & PostTransportOverWrite) { + non_realtime_overwrite (on_entry, finished); + if (!finished) { + g_atomic_int_dec_and_test (&_butler->should_do_transport_work); + goto restart; + } + } + + if (ptw & PostTransportAudition) { non_realtime_set_audition (); } - g_atomic_int_dec_and_test (&butler_should_do_transport_work); + g_atomic_int_dec_and_test (&_butler->should_do_transport_work); + + DEBUG_TRACE (DEBUG::Transport, X_("Butler transport work all done\n")); } void Session::non_realtime_set_speed () { - Glib::RWLock::ReaderLock lm (diskstream_lock); - - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - (*i)->non_realtime_set_speed (); + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->non_realtime_set_speed (); + } } } void -Session::non_realtime_overwrite () +Session::non_realtime_overwrite (int on_entry, bool& finished) { - Glib::RWLock::ReaderLock lm (diskstream_lock); + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && tr->pending_overwrite ()) { + tr->overwrite_existing_buffers (); + } + if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) { + finished = false; + return; + } + } +} + - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if ((*i)->pending_overwrite) { - (*i)->overwrite_existing_buffers (); +void +Session::non_realtime_locate () +{ + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->non_realtime_locate (_transport_frame); } } + + /* XXX: it would be nice to generate the new clicks here (in the non-RT thread) + rather than clearing them so that the RT thread has to spend time constructing + them (in Session::click). + */ + clear_clicks (); } + void -Session::non_realtime_stop (bool abort) +Session::non_realtime_stop (bool abort, int on_entry, bool& finished) { struct tm* now; time_t xnow; bool did_record; - + bool saved; + PostTransportWork ptw = post_transport_work(); + did_record = false; - - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if ((*i)->get_captured_frames () != 0) { + saved = false; + + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && tr->get_captured_frames () != 0) { did_record = true; break; } } /* stop and locate are merged here because they share a lot of common stuff */ - + time (&xnow); now = localtime (&xnow); @@ -290,142 +463,185 @@ Session::non_realtime_stop (bool abort) auditioner->cancel_audition (); } - clear_clicks(); cumulative_rf_motion = 0; reset_rf_scale (0); if (did_record) { - begin_reversible_command ("capture"); - - Location* loc = _locations.end_location(); - bool change_end = false; - - if (_transport_frame < loc->end()) { - - /* stopped recording before current end */ - - if (_end_location_is_free) { - - /* first capture for this session, move end back to where we are */ + begin_reversible_command (Operations::capture); + _have_captured = true; + } - change_end = true; - } + DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: DS stop\n")); - } else if (_transport_frame > loc->end()) { - - /* stopped recording after the current end, extend it */ + if (abort && did_record) { + /* no reason to save the session file when we remove sources + */ + _state_of_the_state = StateOfTheState (_state_of_the_state|InCleanup); + } - change_end = true; - } - - if (change_end) { - add_undo (sigc::retype_return(sigc::bind (mem_fun (*loc, &Location::set_end), loc->end()))); - add_redo (sigc::retype_return(sigc::bind (mem_fun (*loc, &Location::set_end), _transport_frame))); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->transport_stopped_wallclock (*now, xnow, abort); } - - _end_location_is_free = false; - _have_captured = true; } - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - (*i)->transport_stopped (*now, xnow, abort); + if (abort && did_record) { + _state_of_the_state = StateOfTheState (_state_of_the_state & ~InCleanup); } - - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { - if (!(*i)->hidden()) { + + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + if (!(*i)->is_hidden()) { (*i)->set_pending_declick (0); } } if (did_record) { commit_reversible_command (); - } + } if (_engine.running()) { - update_latency_compensation (true, abort); + PostTransportWork ptw = post_transport_work (); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->nonrealtime_handle_transport_stopped (abort, (ptw & PostTransportLocate), (!(ptw & PostTransportLocate) || pending_locate_flush)); + } + update_latency_compensation (); } - if (auto_return || (post_transport_work & PostTransportLocate) || synced_to_jack()) { + bool const auto_return_enabled = + (!config.get_external_sync() && config.get_auto_return()); + + if (auto_return_enabled || + (ptw & PostTransportLocate) || + (_requested_return_frame >= 0) || + synced_to_jack()) { if (pending_locate_flush) { - flush_all_redirects (); + flush_all_inserts (); } - if ((auto_return || synced_to_jack()) && !(post_transport_work & PostTransportLocate)) { + if ((auto_return_enabled || synced_to_jack() || _requested_return_frame >= 0) && + !(ptw & PostTransportLocate)) { - _transport_frame = last_stop_frame; + /* no explicit locate queued */ - if (synced_to_jack()) { - _engine.transport_locate (_transport_frame); - } - } + bool do_locate = false; -#ifndef LEAVE_TRANSPORT_UNADJUSTED - } -#endif + if (_requested_return_frame >= 0) { - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if (!(*i)->hidden()) { - if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) { - (*i)->seek ((jack_nframes_t) (_transport_frame * (double) (*i)->speed())); - } - else { - (*i)->seek (_transport_frame); + /* explicit return request pre-queued in event list. overrides everything else */ + + cerr << "explicit auto-return to " << _requested_return_frame << endl; + + _transport_frame = _requested_return_frame; + do_locate = true; + + } else { + if (config.get_auto_return()) { + + if (play_loop) { + + /* don't try to handle loop play when synced to JACK */ + + if (!synced_to_jack()) { + + Location *location = _locations->auto_loop_location(); + + if (location != 0) { + _transport_frame = location->start(); + } else { + _transport_frame = _last_roll_location; + } + do_locate = true; + } + + } else if (_play_range) { + + /* return to start of range */ + + if (!current_audio_range.empty()) { + _transport_frame = current_audio_range.front().start; + do_locate = true; + } + + } else { + + /* regular auto-return */ + + _transport_frame = _last_roll_location; + do_locate = true; + } } } + + _requested_return_frame = -1; + + if (do_locate) { + _engine.transport_locate (_transport_frame); + } } - deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame); + } + + clear_clicks(); + + /* do this before seeking, because otherwise the tracks will do the wrong thing in seamless loop mode. + */ -#ifdef LEAVE_TRANSPORT_UNADJUSTED + if (ptw & PostTransportClearSubstate) { + _play_range = false; + unset_play_loop (); } -#endif - last_stop_frame = _transport_frame; + /* this for() block can be put inside the previous if() and has the effect of ... ??? what */ - send_full_time_code (); - deliver_mmc (MIDI::MachineControl::cmdStop, 0); - deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame); + DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: locate\n")); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler PTW: locate on %1\n", (*i)->name())); + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && !tr->hidden()) { + tr->non_realtime_locate (_transport_frame); + } - if (did_record) { + if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) { + finished = false; + /* we will be back */ + return; + } + } - /* XXX its a little odd that we're doing this here - when realtime_stop(), which has already executed, - will have done this. - */ + have_looped = false; - if (!Config->get_latched_record_enable()) { - g_atomic_int_set (&_record_status, Disabled); - } else { - g_atomic_int_set (&_record_status, Enabled); + if (!_engine.freewheeling()) { + send_full_time_code (_transport_frame); + + if (!dynamic_cast(_slave)) { + MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop)); + send_mmc_locate (_transport_frame); } - RecordStateChanged (); /* emit signal */ } - - if ((post_transport_work & PostTransportLocate) && get_record_enabled()) { + + if ((ptw & PostTransportLocate) && get_record_enabled()) { /* capture start has been changed, so save pending state */ save_state ("", true); + saved = true; } - /* always try to get rid of this */ + /* always try to get rid of this */ + + remove_pending_capture_state (); - remove_pending_capture_state (); - /* save the current state of things if appropriate */ - if (did_record) { + if (did_record && !saved) { save_state (_current_snapshot_name); } - if (post_transport_work & PostTransportDuration) { - DurationChanged (); /* EMIT SIGNAL */ - } - - if (post_transport_work & PostTransportStop) { + if (ptw & PostTransportStop) { _play_range = false; - - /* do not turn off autoloop on stop */ - + play_loop = false; } PositionChanged (_transport_frame); /* EMIT SIGNAL */ @@ -433,7 +649,7 @@ Session::non_realtime_stop (bool abort) /* and start it up again if relevant */ - if ((post_transport_work & PostTransportLocate) && _slave_type == None && pending_locate_roll) { + if ((ptw & PostTransportLocate) && !config.get_external_sync() && pending_locate_roll) { request_transport_speed (1.0); pending_locate_roll = false; } @@ -446,8 +662,8 @@ Session::check_declick_out () /* this is called after a process() iteration. if PendingDeclickOut was set, it means that we were waiting to declick the output (which has just been - done) before doing something else. this is where we do that "something else". - + done) before maybe doing something else. this is where we do that "something else". + note: called from the audio thread. */ @@ -460,101 +676,121 @@ Session::check_declick_out () stop_transport (pending_abort); transport_sub_state &= ~(PendingDeclickOut|PendingLocate); } + + } else if (transport_sub_state & PendingLoopDeclickOut) { + /* Nothing else to do here; we've declicked, and the loop event will be along shortly */ + transport_sub_state &= ~PendingLoopDeclickOut; } } void -Session::set_auto_loop (bool yn) +Session::unset_play_loop () +{ + play_loop = false; + clear_events (SessionEvent::AutoLoop); + clear_events (SessionEvent::AutoLoopDeclick); + + // set all tracks to NOT use internal looping + boost::shared_ptr rl = routes.reader (); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && !tr->hidden()) { + tr->set_loop (0); + } + } +} + +void +Session::set_play_loop (bool yn) { /* Called from event-handling context */ - - if ((actively_recording() && yn) || _locations.auto_loop_location() == 0) { + + Location *loc; + + if (yn == play_loop || (actively_recording() && yn) || (loc = _locations->auto_loop_location()) == 0) { + /* nothing to do, or can't change loop status while recording */ return; } - - set_dirty(); - if (yn && seamless_loop && synced_to_jack()) { - warning << _("Seamless looping cannot be supported while Ardour is using JACK transport.\n" - "Recommend changing the configured options") + if (yn && Config->get_seamless_loop() && synced_to_jack()) { + warning << string_compose ( + _("Seamless looping cannot be supported while %1 is using JACK transport.\n" + "Recommend changing the configured options"), PROGRAM_NAME) << endmsg; return; } - - if ((auto_loop = yn)) { + if (yn) { - Location *loc; + play_loop = true; - - if ((loc = _locations.auto_loop_location()) != 0) { + if (loc) { - if (seamless_loop) { - // set all diskstreams to use internal looping - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if (!(*i)->hidden()) { - (*i)->set_loop (loc); + unset_play_range (); + + if (Config->get_seamless_loop()) { + // set all tracks to use internal looping + boost::shared_ptr rl = routes.reader (); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && !tr->hidden()) { + tr->set_loop (loc); } } } else { - // set all diskstreams to NOT use internal looping - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if (!(*i)->hidden()) { - (*i)->set_loop (0); + // set all tracks to NOT use internal looping + boost::shared_ptr rl = routes.reader (); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && !tr->hidden()) { + tr->set_loop (0); } } } - - /* stick in the loop event */ - - Event* event = new Event (Event::AutoLoop, Event::Replace, loc->end(), loc->start(), 0.0f); - merge_event (event); - - /* locate to start of loop and roll if current pos is outside of the loop range */ - if (_transport_frame < loc->start() || _transport_frame > loc->end()) { - event = new Event (Event::LocateRoll, Event::Add, Event::Immediate, loc->start(), 0, !synced_to_jack()); - merge_event (event); - } - else { - // locate to current position (+ 1 to force reload) - event = new Event (Event::LocateRoll, Event::Add, Event::Immediate, _transport_frame + 1, 0, !synced_to_jack()); - merge_event (event); - } - } + /* Put the delick and loop events in into the event list. The declick event will + cause a de-clicking fade-out just before the end of the loop, and it will also result + in a fade-in when the loop restarts. The AutoLoop event will peform the actual loop. + */ + framepos_t dcp; + framecnt_t dcl; + auto_loop_declick_range (loc, dcp, dcl); + merge_event (new SessionEvent (SessionEvent::AutoLoopDeclick, SessionEvent::Replace, dcp, dcl, 0.0f)); + merge_event (new SessionEvent (SessionEvent::AutoLoop, SessionEvent::Replace, loc->end(), loc->start(), 0.0f)); - } else { - clear_events (Event::AutoLoop); + /* locate to start of loop and roll. If doing seamless loop, force a + locate+buffer refill even if we are positioned there already. + */ - // set all diskstreams to NOT use internal looping - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if (!(*i)->hidden()) { - (*i)->set_loop (0); - } + start_locate (loc->start(), true, true, false, Config->get_seamless_loop()); } - + + } else { + + unset_play_loop (); } - - ControlChanged (AutoLoop); /* EMIT SIGNAL */ -} + TransportStateChange (); +} void -Session::flush_all_redirects () +Session::flush_all_inserts () { - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { - (*i)->flush_redirects (); + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->flush_processors (); } } void -Session::start_locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop) +Session::start_locate (framepos_t target_frame, bool with_roll, bool with_flush, bool with_loop, bool force) { if (synced_to_jack()) { - float sp; - jack_nframes_t pos; + double sp; + framepos_t pos; _slave->speed_and_position (sp, pos); @@ -573,30 +809,54 @@ Session::start_locate (jack_nframes_t target_frame, bool with_roll, bool with_fl } } else { + locate (target_frame, with_roll, with_flush, with_loop, force); + } +} - locate (target_frame, with_roll, with_flush, with_loop); +int +Session::micro_locate (framecnt_t distance) +{ + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && !tr->can_internal_playback_seek (distance)) { + return -1; + } + } + + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->internal_playback_seek (distance); + } } + + _transport_frame += distance; + return 0; } +/** @param with_mmc true to send a MMC locate command when the locate is done */ void -Session::locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop) +Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool with_loop, bool force, bool with_mmc) { - if (actively_recording()) { + if (actively_recording() && !with_loop) { return; } - if (_transport_frame == target_frame && !loop_changing && !with_loop) { + if (!force && _transport_frame == target_frame && !loop_changing && !with_loop) { if (with_roll) { set_transport_speed (1.0, false); } loop_changing = false; + Located (); /* EMIT SIGNAL */ return; } - _transport_frame = target_frame; - - if (_transport_speed && (!with_loop || loop_changing)) { - /* schedule a declick. we'll be called again when its done */ + if (_transport_speed && !with_loop) { + /* Schedule a declick. We'll be called again when its done. + We only do it this way for ordinary locates, not those + due to loops. + */ if (!(transport_sub_state & PendingDeclickOut)) { transport_sub_state |= (PendingDeclickOut|PendingLocate); @@ -604,138 +864,168 @@ Session::locate (jack_nframes_t target_frame, bool with_roll, bool with_flush, b pending_locate_roll = with_roll; pending_locate_flush = with_flush; return; - } + } } - if (transport_rolling() && !auto_play && !with_roll && !(synced_to_jack() && auto_loop)) { - realtime_stop (false); - } + // Update Timecode time + // [DR] FIXME: find out exactly where this should go below + _transport_frame = target_frame; + timecode_time(_transport_frame, transmitting_timecode_time); + outbound_mtc_timecode_frame = _transport_frame; + next_quarter_frame_to_send = 0; + + /* do "stopped" stuff if: + * + * we are rolling AND + * no autoplay in effect AND + * we're not going to keep rolling after the locate AND + * !(playing a loop with JACK sync) + * + */ + + if (transport_rolling() && (!auto_play_legal || !config.get_auto_play()) && !with_roll && !(synced_to_jack() && play_loop)) { + realtime_stop (false, true); // XXX paul - check if the 2nd arg is really correct + } else { + /* otherwise tell the world that we located */ + realtime_locate (); + } - if ( !with_loop || loop_changing) { + if (force || !with_loop || loop_changing) { + + PostTransportWork todo = PostTransportLocate; - post_transport_work = PostTransportWork (post_transport_work | PostTransportLocate); - if (with_roll) { - post_transport_work = PostTransportWork (post_transport_work | PostTransportRoll); + todo = PostTransportWork (todo | PostTransportRoll); } - schedule_butler_transport_work (); + add_post_transport_work (todo); + _butler->schedule_transport_work (); } else { /* this is functionally what clear_clicks() does but with a tentative lock */ Glib::RWLock::WriterLock clickm (click_lock, Glib::TRY_LOCK); - + if (clickm.locked()) { - + for (Clicks::iterator i = clicks.begin(); i != clicks.end(); ++i) { delete *i; } - + clicks.clear (); } } if (with_roll) { /* switch from input if we're going to roll */ - if (Config->get_use_hardware_monitoring()) { - /* Even though this is called from RT context we are using - a non-tentative rwlock here, because the action must occur. - The rarity and short potential lock duration makes this "OK" - */ - Glib::RWLock::ReaderLock dsm (diskstream_lock); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if ((*i)->record_enabled ()) { - //cerr << "switching from input" << __FILE__ << __LINE__ << endl << endl; - (*i)->monitor_input (!auto_input); - } - } + if (Config->get_monitoring_model() == HardwareMonitoring) { + set_track_monitor_input_status (!config.get_auto_input()); } } else { /* otherwise we're going to stop, so do the opposite */ - if (Config->get_use_hardware_monitoring()) { - /* Even though this is called from RT context we are using - a non-tentative rwlock here, because the action must occur. - The rarity and short potential lock duration makes this "OK" - */ - Glib::RWLock::ReaderLock dsm (diskstream_lock); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if ((*i)->record_enabled ()) { - //cerr << "switching to input" << __FILE__ << __LINE__ << endl << endl; - (*i)->monitor_input (true); - } - } + if (Config->get_monitoring_model() == HardwareMonitoring) { + set_track_monitor_input_status (true); } } - /* cancel autoloop if transport pos outside of loop range */ - if (auto_loop) { - Location* al = _locations.auto_loop_location(); - + /* cancel looped playback if transport pos outside of loop range */ + if (play_loop) { + Location* al = _locations->auto_loop_location(); + if (al && (_transport_frame < al->start() || _transport_frame > al->end())) { // cancel looping directly, this is called from event handling context - set_auto_loop(false); + set_play_loop (false); + } + else if (al && _transport_frame == al->start()) { + if (with_loop) { + // this is only necessary for seamless looping + + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && tr->record_enabled ()) { + // tell it we've looped, so it can deal with the record state + tr->transport_looped(_transport_frame); + } + } + } + have_looped = true; + TransportLooped(); // EMIT SIGNAL } } - + loop_changing = false; + + _send_timecode_update = true; + + if (with_mmc) { + send_mmc_locate (_transport_frame); + } + + Located (); /* EMIT SIGNAL */ } +/** Set the transport speed. + * Called from the process thread. + * @param speed New speed + */ void -Session::set_transport_speed (float speed, bool abort) +Session::set_transport_speed (double speed, bool abort, bool clear_state) { + DEBUG_TRACE (DEBUG::Transport, string_compose ("@ %5 Set transport speed to %1, abort = %2 clear_state = %3, current = %4\n", + speed, abort, clear_state, _transport_speed, _transport_frame)); + if (_transport_speed == speed) { return; } + if (actively_recording() && speed != 1.0 && speed != 0.0) { + /* no varispeed during recording */ + return; + } + + _target_transport_speed = fabs(speed); + + /* 8.0 max speed is somewhat arbitrary but based on guestimates regarding disk i/o capability + and user needs. We really need CD-style "skip" playback for ffwd and rewind. + */ + if (speed > 0) { - speed = min (8.0f, speed); + speed = min (8.0, speed); } else if (speed < 0) { - speed = max (-8.0f, speed); + speed = max (-8.0, speed); } if (transport_rolling() && speed == 0.0) { - if (Config->get_use_hardware_monitoring()) - { - /* Even though this is called from RT context we are using - a non-tentative rwlock here, because the action must occur. - The rarity and short potential lock duration makes this "OK" - */ - Glib::RWLock::ReaderLock dsm (diskstream_lock); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if ((*i)->record_enabled ()) { - //cerr << "switching to input" << __FILE__ << __LINE__ << endl << endl; - (*i)->monitor_input (true); - } - } + /* we are rolling and we want to stop */ + + if (Config->get_monitoring_model() == HardwareMonitoring) { + set_track_monitor_input_status (true); } if (synced_to_jack ()) { + if (clear_state) { + /* do this here because our response to the slave won't + take care of it. + */ + _play_range = false; + unset_play_loop (); + } _engine.transport_stop (); } else { stop_transport (abort); } + unset_play_loop (); + } else if (transport_stopped() && speed == 1.0) { - if (!get_record_enabled() && Config->get_stop_at_session_end() && _transport_frame >= current_end_frame()) { - return; - } + /* we are stopped and we want to start rolling at speed 1 */ - if (Config->get_use_hardware_monitoring()) { - /* Even though this is called from RT context we are using - a non-tentative rwlock here, because the action must occur. - The rarity and short potential lock duration makes this "OK" - */ - Glib::RWLock::ReaderLock dsm (diskstream_lock); - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if (auto_input && (*i)->record_enabled ()) { - //cerr << "switching from input" << __FILE__ << __LINE__ << endl << endl; - (*i)->monitor_input (false); - } - } + if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) { + set_track_monitor_input_status (false); } if (synced_to_jack()) { @@ -746,12 +1036,12 @@ Session::set_transport_speed (float speed, bool abort) } else { - if (!get_record_enabled() && Config->get_stop_at_session_end() && _transport_frame >= current_end_frame()) { - return; - } + /* not zero, not 1.0 ... varispeed */ if ((synced_to_jack()) && speed != 0.0 && speed != 1.0) { - warning << _("Global varispeed cannot be supported while Ardour is connected to JACK transport control") + warning << string_compose ( + _("Global varispeed cannot be supported while %1 is connected to JACK transport control"), + PROGRAM_NAME) << endmsg; return; } @@ -760,50 +1050,66 @@ Session::set_transport_speed (float speed, bool abort) return; } - if (speed > 0.0f && _transport_frame == current_end_frame()) { + if (speed > 0.0 && _transport_frame == current_end_frame()) { return; } - if (speed < 0.0f && _transport_frame == 0) { + if (speed < 0.0 && _transport_frame == 0) { return; } - + clear_clicks (); /* if we are reversing relative to the current speed, or relative to the speed before the last stop, then we have to do extra work. */ - if ((_transport_speed && speed * _transport_speed < 0.0f) || (_last_transport_speed * speed < 0.0f)) { - post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse); + PostTransportWork todo = PostTransportWork (0); + + if ((_transport_speed && speed * _transport_speed < 0.0) || (_last_transport_speed * speed < 0.0) || (_last_transport_speed == 0.0f && speed < 0.0f)) { + todo = PostTransportWork (todo | PostTransportReverse); + _last_roll_or_reversal_location = _transport_frame; } - + _last_transport_speed = _transport_speed; _transport_speed = speed; - - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if ((*i)->realtime_set_speed ((*i)->speed(), true)) { - post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed); + + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && tr->realtime_set_speed (tr->speed(), true)) { + todo = PostTransportWork (todo | PostTransportSpeed); } } - - if (post_transport_work & (PostTransportSpeed|PostTransportReverse)) { - schedule_butler_transport_work (); + + if (todo) { + add_post_transport_work (todo); + _butler->schedule_transport_work (); } + + TransportStateChange (); /* EMIT SIGNAL */ } } + +/** Stop the transport. */ void -Session::stop_transport (bool abort) +Session::stop_transport (bool abort, bool clear_state) { if (_transport_speed == 0.0f) { return; } - if (actively_recording() && !(transport_sub_state & StopPendingCapture) && - _worst_output_latency > current_block_size) - { - + if (actively_recording() && !(transport_sub_state & StopPendingCapture) && worst_input_latency() > current_block_size) { + + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->prepare_to_stop (_transport_frame); + } + } + /* we need to capture the audio that has still not yet been received by the system at the time the stop is requested, so we have to roll past that time. @@ -811,117 +1117,141 @@ Session::stop_transport (bool abort) block before the actual end. we'll declick in the subsequent block, and then we'll really be stopped. */ - - Event *ev = new Event (Event::StopOnce, Event::Replace, - _transport_frame + _worst_output_latency - current_block_size, - 0, 0, abort); - + + DEBUG_TRACE (DEBUG::Transport, string_compose ("stop transport requested @ %1, scheduled for + %2 - %3 = %4, abort = %5\n", + _transport_frame, _worst_input_latency, current_block_size, + _transport_frame - _worst_input_latency - current_block_size, + abort)); + + SessionEvent *ev = new SessionEvent (SessionEvent::StopOnce, SessionEvent::Replace, + _transport_frame + _worst_input_latency - current_block_size, + 0, 0, abort); + merge_event (ev); transport_sub_state |= StopPendingCapture; pending_abort = abort; return; - } + } if ((transport_sub_state & PendingDeclickOut) == 0) { + + if (!(transport_sub_state & StopPendingCapture)) { + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->prepare_to_stop (_transport_frame); + } + } + } + transport_sub_state |= PendingDeclickOut; /* we'll be called again after the declick */ + pending_abort = abort; return; } - realtime_stop (abort); - schedule_butler_transport_work (); + realtime_stop (abort, clear_state); + _butler->schedule_transport_work (); } +/** Called from the process thread */ void Session::start_transport () { + DEBUG_TRACE (DEBUG::Transport, "start_transport\n"); + _last_roll_location = _transport_frame; + _last_roll_or_reversal_location = _transport_frame; + + have_looped = false; /* if record status is Enabled, move it to Recording. if its - already Recording, move it to Disabled. + already Recording, move it to Disabled. */ switch (record_status()) { case Enabled: - if (!punch_in) { + if (!config.get_punch_in()) { enable_record (); } break; case Recording: - disable_record (false); + if (!play_loop) { + disable_record (false); + } break; default: break; } - if (!synced_to_jack() || _exporting) { - actually_start_transport (); - } else { - waiting_to_start = true; - } -} - -void -Session::actually_start_transport () -{ - waiting_to_start = false; - transport_sub_state |= PendingDeclickIn; + _transport_speed = 1.0; - - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - (*i)->realtime_set_speed ((*i)->speed(), true); + _target_transport_speed = 1.0; + + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->realtime_set_speed (tr->speed(), true); + } + (*i)->automation_snapshot (_transport_frame, true); + } + + if (!_engine.freewheeling()) { + Timecode::Time time; + timecode_time_subframes (_transport_frame, time); + if (!dynamic_cast(_slave)) { + MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); + } } - send_mmc_in_another_thread (MIDI::MachineControl::cmdDeferredPlay, 0); - TransportStateChange (); /* EMIT SIGNAL */ } +/** Do any transport work in the audio thread that needs to be done after the + * transport thread is finished. Audio thread, realtime safe. + */ void Session::post_transport () { - if (post_transport_work & PostTransportAudition) { - if (auditioner && auditioner->active()) { + PostTransportWork ptw = post_transport_work (); + + if (ptw & PostTransportAudition) { + if (auditioner && auditioner->auditioning()) { process_function = &Session::process_audition; } else { process_function = &Session::process_with_events; } } - if (post_transport_work & PostTransportStop) { + if (ptw & PostTransportStop) { transport_sub_state = 0; } - if (post_transport_work & PostTransportLocate) { + if (ptw & PostTransportLocate) { - if ((auto_play && !_exporting) || (post_transport_work & PostTransportRoll)) { - + if (((!config.get_external_sync() && (auto_play_legal && config.get_auto_play())) && !_exporting) || (ptw & PostTransportRoll)) { start_transport (); - + } else { transport_sub_state = 0; } } set_next_event (); - - post_transport_work = PostTransportWork (0); -} - -void -Session::set_rf_speed (float speed) -{ - rf_speed = speed; - cumulative_rf_motion = 0; - reset_rf_scale (0); + /* XXX is this really safe? shouldn't we just be unsetting the bits that we actually + know were handled ? + */ + set_post_transport_work (PostTransportWork (0)); } void -Session::reset_rf_scale (jack_nframes_t motion) +Session::reset_rf_scale (framecnt_t motion) { cumulative_rf_motion += motion; @@ -940,228 +1270,234 @@ Session::reset_rf_scale (jack_nframes_t motion) } } -int -Session::set_slave_source (SlaveSource src, jack_nframes_t frame) +void +Session::use_sync_source (Slave* new_slave) { - bool reverse = false; + /* Runs in process() context */ + bool non_rt_required = false; - if (src == _slave_type) { - return 0; - } + /* XXX this deletion is problematic because we're in RT context */ - if (_transport_speed) { - error << _("please stop the transport before adjusting slave settings") << endmsg; - /* help out non-MVC friendly UI's by telling them the slave type changed */ - ControlChanged (SlaveType); /* EMIT SIGNAL */ - return 0; - } + delete _slave; + _slave = new_slave; -// if (src == JACK && Config->get_jack_time_master()) { -// return -1; -// } - - if (_slave) { - delete _slave; - _slave = 0; - _slave_type = None; + send_full_time_code (_transport_frame); + + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr && !tr->hidden()) { + if (tr->realtime_set_speed (tr->speed(), true)) { + non_rt_required = true; + } + tr->set_slaved (_slave != 0); + } } - if (_transport_speed < 0.0) { - reverse = true; + if (non_rt_required) { + add_post_transport_work (PostTransportSpeed); + _butler->schedule_transport_work (); } - switch (src) { - case None: - stop_transport (); - break; + set_dirty(); +} +void +Session::drop_sync_source () +{ + request_sync_source (0); +} + +void +Session::switch_to_sync_source (SyncSource src) +{ + Slave* new_slave; + + DEBUG_TRACE (DEBUG::Slave, string_compose ("Setting up sync source %1\n", enum_2_string (src))); + + switch (src) { case MTC: - if (_mtc_port) { - try { - _slave = new MTC_Slave (*this, *_mtc_port); - } + if (_slave && dynamic_cast(_slave)) { + return; + } - catch (failed_constructor& err) { - return -1; - } + try { + new_slave = new MTC_Slave (*this, *MIDI::Manager::instance()->mtc_input_port()); + } - } else { - error << _("No MTC port defined: MTC slaving is impossible.") << endmsg; - return -1; + catch (failed_constructor& err) { + return; } - _desired_transport_speed = _transport_speed; break; - - case JACK: - _slave = new JACK_Slave (_engine.jack()); - _desired_transport_speed = _transport_speed; + + case MIDIClock: + if (_slave && dynamic_cast(_slave)) { + return; + } + + try { + new_slave = new MIDIClock_Slave (*this, *MIDI::Manager::instance()->midi_clock_input_port(), 24); + } + + catch (failed_constructor& err) { + return; + } break; - }; - - _slave_type = src; - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - if (!(*i)->hidden()) { - if ((*i)->realtime_set_speed ((*i)->speed(), true)) { - non_rt_required = true; - } - (*i)->set_slaved (_slave); + case JACK: + if (_slave && dynamic_cast(_slave)) { + return; } - } - if (reverse) { - reverse_diskstream_buffers (); - } + if (config.get_video_pullup() != 0.0f) { + return; + } - if (non_rt_required) { - post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed); - schedule_butler_transport_work (); - } + new_slave = new JACK_Slave (_engine.jack()); + break; - set_dirty(); - ControlChanged (SlaveType); /* EMIT SIGNAL */ - - return 0; -} + default: + new_slave = 0; + break; + }; -void -Session::reverse_diskstream_buffers () -{ - post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse); - schedule_butler_transport_work (); + request_sync_source (new_slave); } void -Session::set_diskstream_speed (AudioDiskstream* stream, float speed) +Session::set_track_speed (Track* track, double speed) { - if (stream->realtime_set_speed (speed, false)) { - post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed); - schedule_butler_transport_work (); + if (track->realtime_set_speed (speed, false)) { + add_post_transport_work (PostTransportSpeed); + _butler->schedule_transport_work (); set_dirty (); } } void -Session::set_audio_range (list& range) +Session::unset_play_range () { - Event *ev = new Event (Event::SetAudioRange, Event::Add, Event::Immediate, 0, 0.0f); - ev->audio_range = range; - queue_event (ev); + _play_range = false; + _clear_event_type (SessionEvent::RangeStop); + _clear_event_type (SessionEvent::RangeLocate); } void -Session::request_play_range (bool yn) +Session::set_play_range (list& range, bool leave_rolling) { - Event* ev = new Event (Event::SetPlayRange, Event::Add, Event::Immediate, 0, 0.0f, yn); - queue_event (ev); -} + SessionEvent* ev; -void -Session::set_play_range (bool yn) -{ /* Called from event-processing context */ - if (_play_range != yn) { - _play_range = yn; - setup_auto_play (); + unset_play_range (); - if (!_play_range) { + if (range.empty()) { + /* _play_range set to false in unset_play_range() + */ + if (!leave_rolling) { /* stop transport */ - Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0f, false); + SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0f, false); merge_event (ev); } - - ControlChanged (PlayRange); /* EMIT SIGNAL */ + return; } -} -void -Session::setup_auto_play () -{ - /* Called from event-processing context */ + _play_range = true; - Event* ev; - - _clear_event_type (Event::RangeStop); - _clear_event_type (Event::RangeLocate); + /* cancel loop play */ + unset_play_loop (); - if (!_play_range) { - return; - } + list::size_type sz = range.size(); - list::size_type sz = current_audio_range.size(); - if (sz > 1) { - - list::iterator i = current_audio_range.begin(); + + list::iterator i = range.begin(); list::iterator next; - - while (i != current_audio_range.end()) { - + + while (i != range.end()) { + next = i; ++next; - + /* locating/stopping is subject to delays for declicking. */ - - jack_nframes_t requested_frame = (*i).end; - + + framepos_t requested_frame = i->end; + if (requested_frame > current_block_size) { requested_frame -= current_block_size; } else { requested_frame = 0; } - - if (next == current_audio_range.end()) { - ev = new Event (Event::RangeStop, Event::Add, requested_frame, 0, 0.0f); + + if (next == range.end()) { + ev = new SessionEvent (SessionEvent::RangeStop, SessionEvent::Add, requested_frame, 0, 0.0f); } else { - ev = new Event (Event::RangeLocate, Event::Add, requested_frame, (*next).start, 0.0f); + ev = new SessionEvent (SessionEvent::RangeLocate, SessionEvent::Add, requested_frame, (*next).start, 0.0f); } - + merge_event (ev); - + i = next; } - + } else if (sz == 1) { - - ev = new Event (Event::RangeStop, Event::Add, current_audio_range.front().end, 0, 0.0f); + + ev = new SessionEvent (SessionEvent::RangeStop, SessionEvent::Add, range.front().end, 0, 0.0f); merge_event (ev); - - } + + } + + /* save range so we can do auto-return etc. */ + + current_audio_range = range; /* now start rolling at the right place */ - - ev = new Event (Event::LocateRoll, Event::Add, Event::Immediate, current_audio_range.front().start, 0.0f, false); + + ev = new SessionEvent (SessionEvent::LocateRoll, SessionEvent::Add, SessionEvent::Immediate, range.front().start, 0.0f, false); merge_event (ev); + + TransportStateChange (); } void -Session::request_bounded_roll (jack_nframes_t start, jack_nframes_t end) +Session::request_bounded_roll (framepos_t start, framepos_t end) { - request_stop (); - Event *ev = new Event (Event::StopOnce, Event::Replace, Event::Immediate, end, 0.0); + AudioRange ar (start, end, 0); + list lar; + + lar.push_back (ar); + request_play_range (&lar, true); +} +void +Session::request_roll_at_and_return (framepos_t start, framepos_t return_to) +{ + SessionEvent *ev = new SessionEvent (SessionEvent::LocateRollLocate, SessionEvent::Add, SessionEvent::Immediate, return_to, 1.0); + ev->target2_frame = start; queue_event (ev); - request_locate (start, true); } void Session::engine_halted () { + bool ignored; + /* there will be no more calls to process(), so we'd better clean up for ourselves, right now. - but first, make sure the butler is out of + but first, make sure the butler is out of the picture. */ - g_atomic_int_set (&butler_should_do_transport_work, 0); - post_transport_work = PostTransportWork (0); - stop_butler (); - - realtime_stop (false); - non_realtime_stop (false); + if (_butler) { + g_atomic_int_set (&_butler->should_do_transport_work, 0); + set_post_transport_work (PostTransportWork (0)); + _butler->stop (); + } + + realtime_stop (false, true); + non_realtime_stop (false, 0, ignored); transport_sub_state = 0; TransportStateChange (); /* EMIT SIGNAL */ @@ -1171,71 +1507,95 @@ Session::engine_halted () void Session::xrun_recovery () { - if (Config->get_stop_recording_on_xrun() && actively_recording()) { + Xrun (_transport_frame); /* EMIT SIGNAL */ - HaltOnXrun (); /* EMIT SIGNAL */ + if (Config->get_stop_recording_on_xrun() && actively_recording()) { /* it didn't actually halt, but we need to handle things in the same way. */ engine_halted(); - } + } } void -Session::update_latency_compensation (bool with_stop, bool abort) +Session::route_processors_changed (RouteProcessorChange c) { - bool update_jack = false; + if (ignore_route_processor_changes) { + return; + } - if (_state_of_the_state & Deletion) { + if (c.type == RouteProcessorChange::MeterPointChange) { return; } - Glib::RWLock::ReaderLock lm (route_lock); - Glib::RWLock::ReaderLock lm2 (diskstream_lock); - _worst_track_latency = 0; + update_latency_compensation (); + resort_routes (); - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { - if (with_stop) { - (*i)->transport_stopped (abort, (post_transport_work & PostTransportLocate), - (!(post_transport_work & PostTransportLocate) || pending_locate_flush)); - } + set_dirty (); +} + +void +Session::allow_auto_play (bool yn) +{ + auto_play_legal = yn; +} - jack_nframes_t old_latency = (*i)->signal_latency (); - jack_nframes_t track_latency = (*i)->update_total_latency (); +void +Session::reset_jack_connection (jack_client_t* jack) +{ + JACK_Slave* js; - if (old_latency != track_latency) { - update_jack = true; - } - - if (!(*i)->hidden() && ((*i)->active())) { - _worst_track_latency = max (_worst_track_latency, track_latency); - } + if (_slave && ((js = dynamic_cast (_slave)) != 0)) { + js->reset_client (jack); } +} - for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) { - (*i)->set_latency_delay (_worst_track_latency); +bool +Session::maybe_stop (framepos_t limit) +{ + if ((_transport_speed > 0.0f && _transport_frame >= limit) || (_transport_speed < 0.0f && _transport_frame == 0)) { + if (synced_to_jack () && config.get_jack_time_master ()) { + _engine.transport_stop (); + } else if (!synced_to_jack ()) { + stop_transport (); + } + return true; } + return false; +} - /* tell JACK to play catch up */ - - if (update_jack) { - _engine.update_total_latencies (); +void +Session::send_mmc_locate (framepos_t t) +{ + if (!_engine.freewheeling()) { + Timecode::Time time; + timecode_time_subframes (t, time); + MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (time)); } +} - set_worst_io_latencies (false); - - /* reflect any changes in latencies into capture offsets - */ - - for (AudioDiskstreamList::iterator i = audio_diskstreams.begin(); i != audio_diskstreams.end(); ++i) { - (*i)->set_capture_offset (); - } +/** Ask the transport to not send timecode until further notice. The suspension + * will come into effect some finite time after this call, and timecode_transmission_suspended() + * should be checked by the caller to find out when. + */ +void +Session::request_suspend_timecode_transmission () +{ + SessionEvent* ev = new SessionEvent (SessionEvent::SetTimecodeTransmission, SessionEvent::Add, SessionEvent::Immediate, 0, 0, false); + queue_event (ev); } void -Session::update_latency_compensation_proxy (void* ignored) +Session::request_resume_timecode_transmission () +{ + SessionEvent* ev = new SessionEvent (SessionEvent::SetTimecodeTransmission, SessionEvent::Add, SessionEvent::Immediate, 0, 0, true); + queue_event (ev); +} + +bool +Session::timecode_transmission_suspended () const { - update_latency_compensation (false, false); + return g_atomic_int_get (&_suspend_timecode_transmission) == 1; }