diff options
Diffstat (limited to 'libs/ardour/session_transport.cc')
-rw-r--r-- | libs/ardour/session_transport.cc | 1355 |
1 files changed, 1355 insertions, 0 deletions
diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc new file mode 100644 index 0000000000..c86a868e56 --- /dev/null +++ b/libs/ardour/session_transport.cc @@ -0,0 +1,1355 @@ +/* + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <cmath> +#include <cerrno> +#include <unistd.h> + +#include <sigc++/bind.h> +#include <sigc++/retype.h> + +#include <pbd/undo.h> +#include <pbd/error.h> +#include <glibmm/thread.h> +#include <pbd/pthread_utils.h> +#include <pbd/memento_command.h> +#include <pbd/stacktrace.h> + +#include <midi++/mmc.h> +#include <midi++/port.h> + +#include <ardour/ardour.h> +#include <ardour/audioengine.h> +#include <ardour/session.h> +#include <ardour/audio_diskstream.h> +#include <ardour/auditioner.h> +#include <ardour/slave.h> +#include <ardour/location.h> + +#include "i18n.h" + +using namespace std; +using namespace ARDOUR; +using namespace sigc; +using namespace PBD; + +void +Session::request_input_change_handling () +{ + if (!(_state_of_the_state & (InitialConnecting|Deletion))) { + Event* ev = new Event (Event::InputConfigurationChange, Event::Add, Event::Immediate, 0, 0.0); + queue_event (ev); + } +} + +void +Session::request_slave_source (SlaveSource src) +{ + Event* ev = new Event (Event::SetSlaveSource, Event::Add, Event::Immediate, 0, 0.0); + + if (src == JACK) { + /* could set_seamless_loop() be disposed of entirely?*/ + Config->set_seamless_loop (false); + } else { + Config->set_seamless_loop (true); + } + ev->slave = src; + queue_event (ev); +} + +void +Session::request_transport_speed (float speed) +{ + Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, speed); + queue_event (ev); +} + +void +Session::request_diskstream_speed (Diskstream& ds, float speed) +{ + Event* ev = new Event (Event::SetDiskstreamSpeed, Event::Add, Event::Immediate, 0, speed); + ev->set_ptr (&ds); + queue_event (ev); +} + +void +Session::request_stop (bool abort) +{ + Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0, abort); + queue_event (ev); +} + +void +Session::request_locate (nframes_t target_frame, bool with_roll) +{ + Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, false); + queue_event (ev); +} + +void +Session::force_locate (nframes_t target_frame, bool with_roll) +{ + Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, true); + queue_event (ev); +} + +void +Session::request_play_loop (bool yn) +{ + Event* ev; + Location *location = _locations.auto_loop_location(); + + if (location == 0 && yn) { + error << _("Cannot loop - no loop range defined") + << endmsg; + return; + } + + ev = new Event (Event::SetLoop, Event::Add, Event::Immediate, 0, 0.0, yn); + queue_event (ev); + + if (!yn && Config->get_seamless_loop() && transport_rolling()) { + // request an immediate locate to refresh the diskstreams + // after disabling looping + request_locate (_transport_frame-1, false); + } +} + +void +Session::realtime_stop (bool abort) +{ + /* assume that when we start, we'll be moving forwards */ + + // FIXME: where should this really be? [DR] + //send_full_time_code(); + deliver_mmc (MIDI::MachineControl::cmdStop, _transport_frame); + deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame); + + if (_transport_speed < 0.0f) { + post_transport_work = PostTransportWork (post_transport_work | PostTransportStop | PostTransportReverse); + } else { + post_transport_work = PostTransportWork (post_transport_work | PostTransportStop); + } + + 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. + */ + +#ifndef LEAVE_TRANSPORT_UNADJUSTED + decrement_transport_position (_worst_output_latency); +#endif + + /* the duration change is not guaranteed to have happened, but is likely */ + + post_transport_work = PostTransportWork (post_transport_work | PostTransportDuration); + } + + if (abort) { + post_transport_work = PostTransportWork (post_transport_work | PostTransportAbort); + } + + _clear_event_type (Event::StopOnce); + _clear_event_type (Event::RangeStop); + _clear_event_type (Event::RangeLocate); + + disable_record (true); + + reset_slave_state (); + + _transport_speed = 0; + + if (Config->get_use_video_sync()) { + waiting_for_sync_offset = true; + } + + transport_sub_state = ((Config->get_slave_source() == None && Config->get_auto_return()) ? AutoReturning : 0); +} + +void +Session::butler_transport_work () +{ + restart: + bool finished; + boost::shared_ptr<RouteList> r = routes.reader (); + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + int on_entry = g_atomic_int_get (&butler_should_do_transport_work); + finished = true; + + if (post_transport_work & PostTransportCurveRealloc) { + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->curve_reallocate(); + } + } + + if (post_transport_work & PostTransportInputChange) { + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + (*i)->non_realtime_input_change (); + } + } + + if (post_transport_work & PostTransportSpeed) { + non_realtime_set_speed (); + } + + if (post_transport_work & PostTransportReverse) { + + + clear_clicks(); + cumulative_rf_motion = 0; + reset_rf_scale (0); + + /* don't seek if locate will take care of that in non_realtime_stop() */ + + if (!(post_transport_work & PostTransportLocate)) { + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (!(*i)->hidden()) { + if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) { + (*i)->seek ((nframes_t) (_transport_frame * (double) (*i)->speed())); + } + 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 & PostTransportLocate) { + non_realtime_locate (); + } + + if (post_transport_work & PostTransportStop) { + non_realtime_stop (post_transport_work & PostTransportAbort, on_entry, finished); + if (!finished) { + g_atomic_int_dec_and_test (&butler_should_do_transport_work); + goto restart; + } + } + + if (post_transport_work & PostTransportOverWrite) { + non_realtime_overwrite (on_entry, finished); + if (!finished) { + g_atomic_int_dec_and_test (&butler_should_do_transport_work); + goto restart; + } + } + + if (post_transport_work & PostTransportAudition) { + non_realtime_set_audition (); + } + + g_atomic_int_dec_and_test (&butler_should_do_transport_work); +} + +void +Session::non_realtime_set_speed () +{ + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + (*i)->non_realtime_set_speed (); + } +} + +void +Session::non_realtime_overwrite (int on_entry, bool& finished) +{ + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if ((*i)->pending_overwrite) { + (*i)->overwrite_existing_buffers (); + } + if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) { + finished = false; + return; + } + } +} + + +void +Session::non_realtime_locate () +{ + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + (*i)->non_realtime_locate (_transport_frame); + } +} + + +void +Session::non_realtime_stop (bool abort, int on_entry, bool& finished) +{ + struct tm* now; + time_t xnow; + bool did_record; + bool saved; + + did_record = false; + saved = false; + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if ((*i)->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); + + if (auditioner) { + 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 */ + + change_end = true; + } + + } else if (_transport_frame > loc->end()) { + + /* stopped recording after the current end, extend it */ + + change_end = true; + } + + if (change_end) { + XMLNode &before = loc->get_state(); + loc->set_end(_transport_frame); + XMLNode &after = loc->get_state(); + add_command (new MementoCommand<Location>(*loc, &before, &after)); + } + + _end_location_is_free = false; + _have_captured = true; + } + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + (*i)->transport_stopped (*now, xnow, abort); + } + + boost::shared_ptr<RouteList> 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); + } + + if ((Config->get_slave_source() == None && Config->get_auto_return()) || + (post_transport_work & PostTransportLocate) || + (_requested_return_frame >= 0) || + synced_to_jack()) { + + if (pending_locate_flush) { + flush_all_inserts (); + } + + if (((Config->get_slave_source() == None && Config->get_auto_return()) || + synced_to_jack() || + _requested_return_frame >= 0) && + !(post_transport_work & PostTransportLocate)) { + + bool do_locate = false; + + if (_requested_return_frame >= 0) { + _transport_frame = _requested_return_frame; + _requested_return_frame = -1; + do_locate = true; + } else { + _transport_frame = last_stop_frame; + _requested_return_frame = -1; + } + + if (synced_to_jack() && !play_loop) { + do_locate = true; + } + + if (do_locate) { + // cerr << "non-realtimestop: transport locate to " << _transport_frame << endl; + _engine.transport_locate (_transport_frame); + } + } + +#ifndef LEAVE_TRANSPORT_UNADJUSTED + } +#endif + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (!(*i)->hidden()) { + if ((*i)->speed() != 1.0f || (*i)->speed() != -1.0f) { + (*i)->seek ((nframes_t) (_transport_frame * (double) (*i)->speed())); + } + else { + (*i)->seek (_transport_frame); + } + } + if (on_entry != g_atomic_int_get (&butler_should_do_transport_work)) { + finished = false; + /* we will be back */ + return; + } + } + +#ifdef LEAVE_TRANSPORT_UNADJUSTED + } +#endif + + if (_requested_return_frame < 0) { + last_stop_frame = _transport_frame; + } else { + last_stop_frame = _requested_return_frame; + _requested_return_frame = -1; + } + + if (did_record) { + + /* XXX its a little odd that we're doing this here + when realtime_stop(), which has already executed, + will have done this. + JLC - so let's not because it seems unnecessary and breaks loop record + */ +#if 0 + if (!Config->get_latched_record_enable()) { + g_atomic_int_set (&_record_status, Disabled); + } else { + g_atomic_int_set (&_record_status, Enabled); + } + RecordStateChanged (); /* emit signal */ +#endif + } + + if ((post_transport_work & 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 */ + + remove_pending_capture_state (); + + /* save the current state of things if appropriate */ + + if (did_record && !saved) { + save_state (_current_snapshot_name); + } + + if (post_transport_work & PostTransportDuration) { + DurationChanged (); /* EMIT SIGNAL */ + } + + if (post_transport_work & PostTransportStop) { + _play_range = false; + + /* do not turn off autoloop on stop */ + + } + + nframes_t tf = _transport_frame; + + PositionChanged (tf); /* EMIT SIGNAL */ + TransportStateChange (); /* EMIT SIGNAL */ + + /* and start it up again if relevant */ + + if ((post_transport_work & PostTransportLocate) && Config->get_slave_source() == None && pending_locate_roll) { + request_transport_speed (1.0); + pending_locate_roll = false; + } +} + +void +Session::check_declick_out () +{ + bool locate_required = transport_sub_state & PendingLocate; + + /* 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". + + note: called from the audio thread. + */ + + if (transport_sub_state & PendingDeclickOut) { + + if (locate_required) { + start_locate (pending_locate_frame, pending_locate_roll, pending_locate_flush); + transport_sub_state &= ~(PendingDeclickOut|PendingLocate); + } else { + stop_transport (pending_abort); + transport_sub_state &= ~(PendingDeclickOut|PendingLocate); + } + } +} + +void +Session::set_play_loop (bool yn) +{ + /* Called from event-handling context */ + + if ((actively_recording() && yn) || _locations.auto_loop_location() == 0) { + return; + } + + set_dirty(); + + if (yn && Config->get_seamless_loop() && synced_to_jack()) { + warning << _("Seamless looping cannot be supported while Ardour is using JACK transport.\n" + "Recommend changing the configured options") + << endmsg; + return; + } + + + if ((play_loop = yn)) { + + Location *loc; + + + if ((loc = _locations.auto_loop_location()) != 0) { + + if (Config->get_seamless_loop()) { + // set all diskstreams to use internal looping + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (!(*i)->hidden()) { + (*i)->set_loop (loc); + } + } + } + else { + // set all diskstreams to NOT use internal looping + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (!(*i)->hidden()) { + (*i)->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); + } + } + + + + } else { + clear_events (Event::AutoLoop); + + // set all diskstreams to NOT use internal looping + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (!(*i)->hidden()) { + (*i)->set_loop (0); + } + } + + } +} + +void +Session::flush_all_inserts () +{ + boost::shared_ptr<RouteList> r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->flush_processors (); + } +} + +void +Session::start_locate (nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop) +{ + if (synced_to_jack()) { + + float sp; + nframes_t pos; + + _slave->speed_and_position (sp, pos); + + if (target_frame != pos) { + + /* tell JACK to change transport position, and we will + follow along later in ::follow_slave() + */ + + _engine.transport_locate (target_frame); + + if (sp != 1.0f && with_roll) { + _engine.transport_start (); + } + + } + + } else { + + locate (target_frame, with_roll, with_flush, with_loop); + } +} + +void +Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop) +{ + if (actively_recording() && !with_loop) { + return; + } + + if (_transport_frame == target_frame && !loop_changing && !with_loop) { + if (with_roll) { + set_transport_speed (1.0, false); + } + loop_changing = false; + return; + } + + // Update SMPTE time + // [DR] FIXME: find out exactly where this should go below + _transport_frame = target_frame; + smpte_time(_transport_frame, transmitting_smpte_time); + outbound_mtc_smpte_frame = _transport_frame; + next_quarter_frame_to_send = 0; + + if (_transport_speed && (!with_loop || loop_changing)) { + /* schedule a declick. we'll be called again when its done */ + + if (!(transport_sub_state & PendingDeclickOut)) { + transport_sub_state |= (PendingDeclickOut|PendingLocate); + pending_locate_frame = target_frame; + pending_locate_roll = with_roll; + pending_locate_flush = with_flush; + return; + } + } + + if (transport_rolling() && (!auto_play_legal || !Config->get_auto_play()) && !with_roll && !(synced_to_jack() && play_loop)) { + realtime_stop (false); + } + + if ( !with_loop || loop_changing) { + + post_transport_work = PostTransportWork (post_transport_work | PostTransportLocate); + + if (with_roll) { + post_transport_work = PostTransportWork (post_transport_work | PostTransportRoll); + } + + schedule_butler_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_monitoring_model() == HardwareMonitoring) { + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if ((*i)->record_enabled ()) { + //cerr << "switching from input" << __FILE__ << __LINE__ << endl << endl; + (*i)->monitor_input (!Config->get_auto_input()); + } + } + } + } else { + /* otherwise we're going to stop, so do the opposite */ + if (Config->get_monitoring_model() == HardwareMonitoring) { + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if ((*i)->record_enabled ()) { + //cerr << "switching to input" << __FILE__ << __LINE__ << endl << endl; + (*i)->monitor_input (true); + } + } + } + } + + /* 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_play_loop (false); + } + else if (al && _transport_frame == al->start()) { + if (with_loop) { + // this is only necessary for seamless looping + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if ((*i)->record_enabled ()) { + // tell it we've looped, so it can deal with the record state + (*i)->transport_looped(_transport_frame); + } + } + } + + TransportLooped(); // EMIT SIGNAL + } + } + + loop_changing = false; + + _send_smpte_update = true; +} + +/** Set the transport speed. + * @param speed New speed + * @param abort + */ +void +Session::set_transport_speed (float speed, bool abort) +{ + if (_transport_speed == speed) { + return; + } + + if (speed > 0) { + speed = min (8.0f, speed); + } else if (speed < 0) { + speed = max (-8.0f, speed); + } + + if (transport_rolling() && speed == 0.0) { + + /* we are rolling and we want to stop */ + + if (Config->get_monitoring_model() == HardwareMonitoring) + { + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if ((*i)->record_enabled ()) { + //cerr << "switching to input" << __FILE__ << __LINE__ << endl << endl; + (*i)->monitor_input (true); + } + } + } + + if (synced_to_jack ()) { + _engine.transport_stop (); + } else { + stop_transport (abort); + } + + } else if (transport_stopped() && speed == 1.0) { + + /* we are stopped and we want to start rolling at speed 1 */ + + if (!get_record_enabled() && Config->get_stop_at_session_end() && _transport_frame >= current_end_frame()) { + return; + } + + if (Config->get_monitoring_model() == HardwareMonitoring) { + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (Config->get_auto_input() && (*i)->record_enabled ()) { + //cerr << "switching from input" << __FILE__ << __LINE__ << endl << endl; + (*i)->monitor_input (false); + } + } + } + + if (synced_to_jack()) { + _engine.transport_start (); + } else { + start_transport (); + } + + } else { + + if (!get_record_enabled() && Config->get_stop_at_session_end() && _transport_frame >= current_end_frame()) { + return; + } + + if ((synced_to_jack()) && speed != 0.0 && speed != 1.0) { + warning << _("Global varispeed cannot be supported while Ardour is connected to JACK transport control") + << endmsg; + return; + } + + if (actively_recording()) { + return; + } + + if (speed > 0.0f && _transport_frame == current_end_frame()) { + return; + } + + if (speed < 0.0f && _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) || (_last_transport_speed == 0.0f && speed < 0.0f)) { + post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse); + } + + _last_transport_speed = _transport_speed; + _transport_speed = speed; + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if ((*i)->realtime_set_speed ((*i)->speed(), true)) { + post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed); + } + } + + if (post_transport_work & (PostTransportSpeed|PostTransportReverse)) { + schedule_butler_transport_work (); + } + } +} + + +/** Stop the transport. */ +void +Session::stop_transport (bool abort) +{ + if (_transport_speed == 0.0f) { + return; + } + + if (actively_recording() && !(transport_sub_state & StopPendingCapture) && + _worst_output_latency > current_block_size) + { + + /* 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. + + we want to declick before stopping, so schedule the autostop for one + 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); + + merge_event (ev); + transport_sub_state |= StopPendingCapture; + pending_abort = abort; + return; + } + + + if ((transport_sub_state & PendingDeclickOut) == 0) { + transport_sub_state |= PendingDeclickOut; + /* we'll be called again after the declick */ + pending_abort = abort; + return; + } + + realtime_stop (abort); + schedule_butler_transport_work (); +} + +void +Session::start_transport () +{ + _last_roll_location = _transport_frame; + + /* if record status is Enabled, move it to Recording. if its + already Recording, move it to Disabled. + */ + + switch (record_status()) { + case Enabled: + if (!Config->get_punch_in()) { + enable_record (); + } + break; + + case Recording: + 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; + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + (*i)->realtime_set_speed ((*i)->speed(), true); + } + + deliver_mmc(MIDI::MachineControl::cmdDeferredPlay, _transport_frame); + + 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()) { + process_function = &Session::process_audition; + } else { + process_function = &Session::process_with_events; + } + } + + if (post_transport_work & PostTransportStop) { + + transport_sub_state = 0; + } + + if (post_transport_work & PostTransportLocate) { + + if (((Config->get_slave_source() == None && (auto_play_legal && Config->get_auto_play())) && !_exporting) || (post_transport_work & PostTransportRoll)) { + start_transport (); + + } else { + transport_sub_state = 0; + } + } + + set_next_event (); + + post_transport_work = PostTransportWork (0); +} + +void +Session::reset_rf_scale (nframes_t motion) +{ + cumulative_rf_motion += motion; + + if (cumulative_rf_motion < 4 * _current_frame_rate) { + rf_scale = 1; + } else if (cumulative_rf_motion < 8 * _current_frame_rate) { + rf_scale = 4; + } else if (cumulative_rf_motion < 16 * _current_frame_rate) { + rf_scale = 10; + } else { + rf_scale = 100; + } + + if (motion != 0) { + set_dirty(); + } +} + +void +Session::set_slave_source (SlaveSource src) +{ + bool reverse = false; + bool non_rt_required = false; + + if (_transport_speed) { + error << _("please stop the transport before adjusting slave settings") << endmsg; + return; + } + +// if (src == JACK && Config->get_jack_time_master()) { +// return; +// } + + if (_slave) { + delete _slave; + _slave = 0; + } + + if (_transport_speed < 0.0) { + reverse = true; + } + + switch (src) { + case None: + stop_transport (); + break; + + case MTC: + if (_mtc_port) { + try { + _slave = new MTC_Slave (*this, *_mtc_port); + } + + catch (failed_constructor& err) { + return; + } + + } else { + error << _("No MTC port defined: MTC slaving is impossible.") << endmsg; + return; + } + _desired_transport_speed = _transport_speed; + break; + + case JACK: + _slave = new JACK_Slave (_engine.jack()); + _desired_transport_speed = _transport_speed; + break; + }; + + Config->set_slave_source (src); + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + if (!(*i)->hidden()) { + if ((*i)->realtime_set_speed ((*i)->speed(), true)) { + non_rt_required = true; + } + (*i)->set_slaved (_slave); + } + } + + if (reverse) { + reverse_diskstream_buffers (); + } + + if (non_rt_required) { + post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed); + schedule_butler_transport_work (); + } + + set_dirty(); +} + +void +Session::reverse_diskstream_buffers () +{ + post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse); + schedule_butler_transport_work (); +} + +void +Session::set_diskstream_speed (Diskstream* stream, float speed) +{ + if (stream->realtime_set_speed (speed, false)) { + post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed); + schedule_butler_transport_work (); + set_dirty (); + } +} + +void +Session::set_audio_range (list<AudioRange>& range) +{ + Event *ev = new Event (Event::SetAudioRange, Event::Add, Event::Immediate, 0, 0.0f); + ev->audio_range = range; + queue_event (ev); +} + +void +Session::request_play_range (bool yn) +{ + Event* ev = new Event (Event::SetPlayRange, Event::Add, Event::Immediate, 0, 0.0f, yn); + queue_event (ev); +} + +void +Session::set_play_range (bool yn) +{ + /* Called from event-processing context */ + + if (_play_range != yn) { + _play_range = yn; + setup_auto_play (); + + if (!_play_range) { + /* stop transport */ + Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0f, false); + merge_event (ev); + } + } +} + +void +Session::setup_auto_play () +{ + /* Called from event-processing context */ + + Event* ev; + + _clear_event_type (Event::RangeStop); + _clear_event_type (Event::RangeLocate); + + if (!_play_range) { + return; + } + + list<AudioRange>::size_type sz = current_audio_range.size(); + + if (sz > 1) { + + list<AudioRange>::iterator i = current_audio_range.begin(); + list<AudioRange>::iterator next; + + while (i != current_audio_range.end()) { + + next = i; + ++next; + + /* locating/stopping is subject to delays for declicking. + */ + + nframes_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); + } else { + ev = new Event (Event::RangeLocate, Event::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); + merge_event (ev); + + } + + /* now start rolling at the right place */ + + ev = new Event (Event::LocateRoll, Event::Add, Event::Immediate, current_audio_range.front().start, 0.0f, false); + merge_event (ev); +} + +void +Session::request_roll_at_and_return (nframes_t start, nframes_t return_to) +{ + Event *ev = new Event (Event::LocateRollLocate, Event::Add, Event::Immediate, return_to, 1.0); + ev->target2_frame = start; + queue_event (ev); +} + +void +Session::request_bounded_roll (nframes_t start, nframes_t end) +{ + request_stop (); + Event *ev = new Event (Event::StopOnce, Event::Replace, end, Event::Immediate, 0.0); + 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 + 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, 0, ignored); + transport_sub_state = 0; + + TransportStateChange (); /* EMIT SIGNAL */ +} + + +void +Session::xrun_recovery () +{ + Xrun (transport_frame()); //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) +{ + bool update_jack = false; + + if (_state_of_the_state & Deletion) { + return; + } + + _worst_track_latency = 0; + + boost::shared_ptr<RouteList> r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + + if (with_stop) { + (*i)->handle_transport_stopped (abort, (post_transport_work & PostTransportLocate), + (!(post_transport_work & PostTransportLocate) || pending_locate_flush)); + } + + nframes_t old_latency = (*i)->signal_latency (); + nframes_t track_latency = (*i)->update_total_latency (); + + if (old_latency != track_latency) { + (*i)->update_port_total_latencies (); + update_jack = true; + } + + if (!(*i)->is_hidden() && ((*i)->active())) { + _worst_track_latency = max (_worst_track_latency, track_latency); + } + } + + if (update_jack) { + _engine.update_total_latencies (); + } + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + (*i)->set_latency_delay (_worst_track_latency); + } + + set_worst_io_latencies (); + + /* reflect any changes in latencies into capture offsets + */ + + boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader(); + + for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { + (*i)->set_capture_offset (); + } +} + +void +Session::allow_auto_play (bool yn) +{ + auto_play_legal = yn; +} + +void +Session::reset_jack_connection (jack_client_t* jack) +{ + JACK_Slave* js; + + if (_slave && ((js = dynamic_cast<JACK_Slave*> (_slave)) != 0)) { + js->reset_client (jack); + } +} |