X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession_transport.cc;h=b1eb5caf27724f547ec8d5410b4e83522e25590c;hb=f97091105ddab0d78a73b67789a7a8a17101b329;hp=686ca4ce733327afad57d650fa80fca9f6233e98;hpb=2cb5595b11df77375d199c4bed3e35361fba52a3;p=ardour.git diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 686ca4ce73..b1eb5caf27 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -21,6 +21,10 @@ #include #include +#ifdef WAF_BUILD +#include "libardour-config.h" +#endif + #include "pbd/undo.h" #include "pbd/error.h" @@ -30,16 +34,18 @@ #include "midi++/mmc.h" #include "midi++/port.h" +#include "midi++/manager.h" #include "ardour/ardour.h" -#include "ardour/audio_diskstream.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" @@ -106,11 +112,25 @@ Session::request_transport_speed (double 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_diskstream_speed (Diskstream& ds, double speed) +Session::request_transport_speed_nonzero (double speed) { - SessionEvent* ev = new SessionEvent (SessionEvent::SetDiskstreamSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, speed); - ev->set_ptr (&ds); + if (speed == 0) { + speed = DBL_EPSILON; + } + + request_transport_speed (speed); +} + +void +Session::request_track_speed (Track* tr, double speed) +{ + SessionEvent* ev = new SessionEvent (SessionEvent::SetTrackSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, speed); + ev->set_ptr (tr); queue_event (ev); } @@ -123,7 +143,7 @@ Session::request_stop (bool abort, bool clear_state) } void -Session::request_locate (nframes_t target_frame, bool with_roll) +Session::request_locate (framepos_t target_frame, bool with_roll) { 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)); @@ -131,7 +151,7 @@ Session::request_locate (nframes_t target_frame, bool with_roll) } void -Session::force_locate (nframes64_t target_frame, bool with_roll) +Session::force_locate (framepos_t target_frame, bool with_roll) { 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)); @@ -142,7 +162,7 @@ void Session::request_play_loop (bool yn, bool leave_rolling) { SessionEvent* ev; - Location *location = _locations.auto_loop_location(); + Location *location = _locations->auto_loop_location(); if (location == 0 && yn) { error << _("Cannot loop - no loop range defined") @@ -155,7 +175,7 @@ Session::request_play_loop (bool yn, bool leave_rolling) queue_event (ev); if (!leave_rolling && !yn && Config->get_seamless_loop() && transport_rolling()) { - // request an immediate locate to refresh the diskstreams + // request an immediate locate to refresh the tracks // after disabling looping request_locate (_transport_frame-1, false); } @@ -177,22 +197,25 @@ Session::request_play_range (list* range, bool leave_rolling) void Session::realtime_stop (bool abort, bool clear_state) { - DEBUG_TRACE (DEBUG::Transport, "realtime stop\n"); + 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 */ - // FIXME: where should this really be? [DR] - //send_full_time_code(); - deliver_mmc (MIDI::MachineControl::cmdStop, 0); - deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame); - if (_transport_speed < 0.0f) { todo = (PostTransportWork (todo | PostTransportStop | PostTransportReverse)); } else { 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 @@ -200,14 +223,14 @@ Session::realtime_stop (bool abort, bool clear_state) past that point to pick up delayed input (and/or to delick) */ - if (_worst_output_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_output_latency - current_block_size)); - } else { - decrement_transport_position (current_block_size); - } + 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 */ @@ -230,18 +253,31 @@ Session::realtime_stop (bool abort, bool clear_state) _clear_event_type (SessionEvent::RangeStop); _clear_event_type (SessionEvent::RangeLocate); - disable_record (true); + /* 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); + if (config.get_use_video_sync()) { waiting_for_sync_offset = true; } - transport_sub_state = ((!config.get_external_sync()&& config.get_auto_return()) ? AutoReturning : 0); + 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 @@ -251,14 +287,34 @@ Session::butler_transport_work () bool finished; PostTransportWork ptw; boost::shared_ptr r = routes.reader (); - boost::shared_ptr dsl = diskstreams.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(); @@ -266,8 +322,11 @@ Session::butler_transport_work () } if (ptw & PostTransportInputChange) { - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - (*i)->non_realtime_input_change (); + 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 (); + } } } @@ -285,9 +344,10 @@ Session::butler_transport_work () if (!(ptw & PostTransportLocate)) { - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if (!(*i)->hidden()) { - (*i)->non_realtime_locate (_transport_frame); + 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); } if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) { /* new request, stop seeking, and start again */ @@ -330,21 +390,23 @@ Session::butler_transport_work () void Session::non_realtime_set_speed () { - boost::shared_ptr dsl = diskstreams.reader(); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->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 (int on_entry, bool& finished) { - boost::shared_ptr dsl = diskstreams.reader(); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if ((*i)->pending_overwrite) { - (*i)->overwrite_existing_buffers (); + 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; @@ -357,11 +419,19 @@ Session::non_realtime_overwrite (int on_entry, bool& finished) void Session::non_realtime_locate () { - boost::shared_ptr dsl = diskstreams.reader(); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - (*i)->non_realtime_locate (_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->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 (); } @@ -377,10 +447,10 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) did_record = false; saved = false; - boost::shared_ptr dsl = diskstreams.reader(); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if ((*i)->get_captured_frames () != 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->get_captured_frames () != 0) { did_record = true; break; } @@ -400,43 +470,27 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) reset_rf_scale (0); if (did_record) { - begin_reversible_command ("capture"); - - Location* loc = _locations.session_range_location(); - bool change_end = false; - - if (_transport_frame < loc->end()) { - - /* stopped recording before current end */ - - if (config.get_end_marker_is_free()) { - - /* first capture for this session, move end back to where we are */ - - change_end = true; - } - - } else if (_transport_frame > loc->end()) { + begin_reversible_command (Operations::capture); + _have_captured = true; + } - /* stopped recording after the current end, extend it */ + DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: DS stop\n")); - change_end = true; - } + 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); + } - if (change_end) { - XMLNode &before = loc->get_state(); - loc->set_end(_transport_frame); - XMLNode &after = loc->get_state(); - add_command (new MementoCommand(*loc, &before, &after)); + 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); } - - config.set_end_marker_is_free (false); - _have_captured = true; } - DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: DS stop\n")); - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - (*i)->transport_stopped (*now, xnow, abort); + if (abort && did_record) { + _state_of_the_state = StateOfTheState (_state_of_the_state & ~InCleanup); } boost::shared_ptr r = routes.reader (); @@ -452,7 +506,11 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) } 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 (); } bool const auto_return_enabled = @@ -470,14 +528,14 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) if ((auto_return_enabled || synced_to_jack() || _requested_return_frame >= 0) && !(ptw & PostTransportLocate)) { - /* no explicit locate queued */ + /* no explicit locate queued */ bool do_locate = false; if (_requested_return_frame >= 0) { /* 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; @@ -492,8 +550,8 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) if (!synced_to_jack()) { - Location *location = _locations.auto_loop_location(); - + Location *location = _locations->auto_loop_location(); + if (location != 0) { _transport_frame = location->start(); } else { @@ -512,25 +570,25 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) } } else { - + /* regular auto-return */ - + _transport_frame = _last_roll_location; do_locate = true; } - } + } } - _requested_return_frame = -1; + _requested_return_frame = -1; if (do_locate) { _engine.transport_locate (_transport_frame); } - } + } } - /* do this before seeking, because otherwise the Diskstreams will do the wrong thing in seamless loop mode. + /* do this before seeking, because otherwise the tracks will do the wrong thing in seamless loop mode. */ if (ptw & PostTransportClearSubstate) { @@ -540,13 +598,12 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) /* this for() block can be put inside the previous if() and has the effect of ... ??? what */ - DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: locate\n")); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if (!(*i)->hidden()) { - DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler PTW: locate on %1\n", (*i)->name())); - (*i)->non_realtime_locate (_transport_frame); + 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 (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) { @@ -558,9 +615,12 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) have_looped = false; - send_full_time_code (0); - deliver_mmc (MIDI::MachineControl::cmdStop, 0); - deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame); + 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); + } if ((ptw & PostTransportLocate) && get_record_enabled()) { /* capture start has been changed, so save pending state */ @@ -568,9 +628,9 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) 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 */ @@ -578,18 +638,12 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) save_state (_current_snapshot_name); } - if (ptw & PostTransportDuration) { - DurationChanged (); /* EMIT SIGNAL */ - } - if (ptw & PostTransportStop) { _play_range = false; play_loop = false; } - // can't cast away volatile so copy and emit that - nframes64_t tframe = _transport_frame; - PositionChanged (tframe); /* EMIT SIGNAL */ + PositionChanged (_transport_frame); /* EMIT SIGNAL */ TransportStateChange (); /* EMIT SIGNAL */ /* and start it up again if relevant */ @@ -629,12 +683,13 @@ Session::unset_play_loop () { play_loop = false; clear_events (SessionEvent::AutoLoop); - - // set all diskstreams to NOT use internal looping - boost::shared_ptr dsl = diskstreams.reader(); - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->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); } } } @@ -646,20 +701,19 @@ Session::set_play_loop (bool yn) Location *loc; - if (yn == play_loop || (actively_recording() && yn) || (loc = _locations.auto_loop_location()) == 0) { + 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 && 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) + 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 (yn) { play_loop = true; @@ -669,30 +723,32 @@ Session::set_play_loop (bool yn) unset_play_range (); if (Config->get_seamless_loop()) { - // set all diskstreams to use internal looping - boost::shared_ptr dsl = diskstreams.reader(); - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if (!(*i)->hidden()) { - (*i)->set_loop (loc); + // 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 - boost::shared_ptr dsl = diskstreams.reader(); - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->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); } } } - + /* put the loop event into the event list */ - + SessionEvent* event = new SessionEvent (SessionEvent::AutoLoop, SessionEvent::Replace, loc->end(), loc->start(), 0.0f); merge_event (event); - /* locate to start of loop and roll. If doing seamless loop, force a + /* locate to start of loop and roll. If doing seamless loop, force a locate+buffer refill even if we are positioned there already. */ @@ -717,12 +773,12 @@ Session::flush_all_inserts () } void -Session::start_locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool with_loop, bool force) +Session::start_locate (framepos_t target_frame, bool with_roll, bool with_flush, bool with_loop, bool force) { if (synced_to_jack()) { double sp; - nframes64_t pos; + framepos_t pos; _slave->speed_and_position (sp, pos); @@ -746,26 +802,30 @@ Session::start_locate (nframes64_t target_frame, bool with_roll, bool with_flush } int -Session::micro_locate (nframes_t distance) +Session::micro_locate (framecnt_t distance) { - boost::shared_ptr dsl = diskstreams.reader(); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if (!(*i)->can_internal_playback_seek (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 (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - (*i)->internal_playback_seek (distance); + 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 (nframes64_t target_frame, bool with_roll, bool with_flush, bool with_loop, bool force) +Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool with_loop, bool force, bool with_mmc) { if (actively_recording() && !with_loop) { return; @@ -776,17 +836,11 @@ Session::locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool set_transport_speed (1.0, false); } loop_changing = false; + Located (); /* EMIT SIGNAL */ return; } - // 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; - - if (_transport_speed && (!with_loop || loop_changing)) { + if (_transport_speed) { /* schedule a declick. we'll be called again when its done */ if (!(transport_sub_state & PendingDeclickOut)) { @@ -798,8 +852,27 @@ Session::locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool } } + // 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 (force || !with_loop || loop_changing) { @@ -808,7 +881,7 @@ Session::locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool if (with_roll) { todo = PostTransportWork (todo | PostTransportRoll); - } + } add_post_transport_work (todo); _butler->schedule_transport_work (); @@ -832,33 +905,18 @@ Session::locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool if (with_roll) { /* switch from input if we're going to roll */ if (Config->get_monitoring_model() == HardwareMonitoring) { - - boost::shared_ptr 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()); - } - } + set_track_monitor_input_status (!config.get_auto_input()); } } else { /* otherwise we're going to stop, so do the opposite */ if (Config->get_monitoring_model() == HardwareMonitoring) { - boost::shared_ptr 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); - } - } + set_track_monitor_input_status (true); } } /* cancel looped playback if transport pos outside of loop range */ if (play_loop) { - Location* al = _locations.auto_loop_location(); + 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 @@ -868,12 +926,12 @@ Session::locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool if (with_loop) { // this is only necessary for seamless looping - boost::shared_ptr dsl = diskstreams.reader(); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if ((*i)->record_enabled ()) { + 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 - (*i)->transport_looped(_transport_frame); + tr->transport_looped(_transport_frame); } } } @@ -886,22 +944,32 @@ Session::locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool _send_timecode_update = true; + if (with_mmc) { + send_mmc_locate (_transport_frame); + } + Located (); /* EMIT SIGNAL */ } /** Set the transport speed. - * @param speed New speed - * @param abort + * Called from the process thread. + * @param speed New speed */ void Session::set_transport_speed (double speed, bool abort, bool clear_state) { - DEBUG_TRACE (DEBUG::Transport, string_compose ("Set transport speed to %1, abort = %2 clear_state = %3, current = %4\n", speed, abort, clear_state, _transport_speed)); + 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 @@ -918,21 +986,13 @@ Session::set_transport_speed (double speed, bool abort, bool clear_state) /* we are rolling and we want to stop */ - if (Config->get_monitoring_model() == HardwareMonitoring) - { - boost::shared_ptr 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 (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 + /* do this here because our response to the slave won't take care of it. */ _play_range = false; @@ -943,20 +1003,14 @@ Session::set_transport_speed (double speed, bool abort, bool clear_state) stop_transport (abort); } + unset_play_loop (); + } else if (transport_stopped() && speed == 1.0) { /* we are stopped and we want to start rolling at speed 1 */ - if (Config->get_monitoring_model() == HardwareMonitoring) { - - boost::shared_ptr 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 (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) { + set_track_monitor_input_status (false); } if (synced_to_jack()) { @@ -967,9 +1021,12 @@ Session::set_transport_speed (double speed, bool abort, bool clear_state) } else { + /* not zero, not 1.0 ... varispeed */ + if ((synced_to_jack()) && speed != 0.0 && speed != 1.0) { - warning << string_compose (_("Global varispeed cannot be supported while %1 is connected to JACK transport control"), - PROGRAM_NAME) + warning << string_compose ( + _("Global varispeed cannot be supported while %1 is connected to JACK transport control"), + PROGRAM_NAME) << endmsg; return; } @@ -991,21 +1048,22 @@ Session::set_transport_speed (double speed, bool abort, bool clear_state) /* 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. */ - + 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; - boost::shared_ptr dsl = diskstreams.reader(); - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if ((*i)->realtime_set_speed ((*i)->speed(), true)) { + 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); - break; } } @@ -1013,6 +1071,8 @@ Session::set_transport_speed (double speed, bool abort, bool clear_state) add_post_transport_work (todo); _butler->schedule_transport_work (); } + + TransportStateChange (); /* EMIT SIGNAL */ } } @@ -1025,13 +1085,15 @@ Session::stop_transport (bool abort, bool clear_state) return; } - if (actively_recording() && !(transport_sub_state & StopPendingCapture) && _worst_output_latency > current_block_size) { - - boost::shared_ptr dsl = diskstreams.reader(); + if (actively_recording() && !(transport_sub_state & StopPendingCapture) && worst_input_latency() > current_block_size) { - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - (*i)->prepare_to_stop (_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->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. @@ -1041,9 +1103,14 @@ Session::stop_transport (bool abort, bool clear_state) and then we'll really be stopped. */ + 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_output_latency - current_block_size, - 0, 0, abort); + _transport_frame + _worst_input_latency - current_block_size, + 0, 0, abort); merge_event (ev); transport_sub_state |= StopPendingCapture; @@ -1051,17 +1118,18 @@ Session::stop_transport (bool abort, bool clear_state) return; } - if ((transport_sub_state & PendingDeclickOut) == 0) { - if (!(transport_sub_state & StopPendingCapture)) { - boost::shared_ptr dsl = diskstreams.reader(); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - (*i)->prepare_to_stop (_transport_frame); - } - } - + 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; @@ -1072,10 +1140,15 @@ Session::stop_transport (bool abort, bool 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 @@ -1104,12 +1177,20 @@ Session::start_transport () _transport_speed = 1.0; _target_transport_speed = 1.0; - boost::shared_ptr dsl = diskstreams.reader(); - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - (*i)->realtime_set_speed ((*i)->speed(), true); + 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); } - deliver_mmc(MIDI::MachineControl::cmdDeferredPlay, _transport_frame); + Timecode::Time time; + timecode_time_subframes (_transport_frame, time); + if (!dynamic_cast(_slave)) { + MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); + } TransportStateChange (); /* EMIT SIGNAL */ } @@ -1153,7 +1234,7 @@ Session::post_transport () } void -Session::reset_rf_scale (nframes_t motion) +Session::reset_rf_scale (framecnt_t motion) { cumulative_rf_motion += motion; @@ -1184,13 +1265,16 @@ Session::use_sync_source (Slave* new_slave) delete _slave; _slave = new_slave; - boost::shared_ptr dsl = diskstreams.reader(); - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - if (!(*i)->hidden()) { - if ((*i)->realtime_set_speed ((*i)->speed(), true)) { + 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; } - (*i)->set_slaved (_slave != 0); + tr->set_slaved (_slave != 0); } } @@ -1221,17 +1305,11 @@ Session::switch_to_sync_source (SyncSource src) return; } - if (_mtc_port) { - try { - new_slave = new MTC_Slave (*this, *_mtc_port); - } - - catch (failed_constructor& err) { - return; - } + try { + new_slave = new MTC_Slave (*this, *MIDI::Manager::instance()->mtc_input_port()); + } - } else { - error << _("No MTC port defined: MTC slaving is impossible.") << endmsg; + catch (failed_constructor& err) { return; } break; @@ -1241,17 +1319,11 @@ Session::switch_to_sync_source (SyncSource src) return; } - if (_midi_clock_port) { - try { - new_slave = new MIDIClock_Slave (*this, *_midi_clock_port, 24); - } - - catch (failed_constructor& err) { - return; - } + try { + new_slave = new MIDIClock_Slave (*this, *MIDI::Manager::instance()->midi_clock_input_port(), 24); + } - } else { - error << _("No MIDI Clock port defined: MIDI Clock slaving is impossible.") << endmsg; + catch (failed_constructor& err) { return; } break; @@ -1261,9 +1333,13 @@ Session::switch_to_sync_source (SyncSource src) return; } + if (config.get_video_pullup() != 0.0f) { + return; + } + new_slave = new JACK_Slave (_engine.jack()); break; - + default: new_slave = 0; break; @@ -1273,16 +1349,9 @@ Session::switch_to_sync_source (SyncSource src) } void -Session::reverse_diskstream_buffers () +Session::set_track_speed (Track* track, double speed) { - add_post_transport_work (PostTransportReverse); - _butler->schedule_transport_work (); -} - -void -Session::set_diskstream_speed (Diskstream* stream, double speed) -{ - if (stream->realtime_set_speed (speed, false)) { + if (track->realtime_set_speed (speed, false)) { add_post_transport_work (PostTransportSpeed); _butler->schedule_transport_work (); set_dirty (); @@ -1305,7 +1374,7 @@ Session::set_play_range (list& range, bool leave_rolling) /* Called from event-processing context */ unset_play_range (); - + if (range.empty()) { /* _play_range set to false in unset_play_range() */ @@ -1323,45 +1392,45 @@ Session::set_play_range (list& range, bool leave_rolling) unset_play_loop (); list::size_type sz = range.size(); - + if (sz > 1) { - - list::iterator i = range.begin(); + + list::iterator i = range.begin(); list::iterator next; - + while (i != range.end()) { - + next = i; ++next; - + /* locating/stopping is subject to delays for declicking. */ - - 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 == range.end()) { ev = new SessionEvent (SessionEvent::RangeStop, SessionEvent::Add, requested_frame, 0, 0.0f); } else { ev = new SessionEvent (SessionEvent::RangeLocate, SessionEvent::Add, requested_frame, (*next).start, 0.0f); } - + merge_event (ev); - + i = next; } - + } else if (sz == 1) { 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. */ @@ -1371,12 +1440,12 @@ Session::set_play_range (list& range, bool leave_rolling) ev = new SessionEvent (SessionEvent::LocateRoll, SessionEvent::Add, SessionEvent::Immediate, range.front().start, 0.0f, false); merge_event (ev); - + TransportStateChange (); } void -Session::request_bounded_roll (nframes_t start, nframes_t end) +Session::request_bounded_roll (framepos_t start, framepos_t end) { AudioRange ar (start, end, 0); list lar; @@ -1385,7 +1454,7 @@ Session::request_bounded_roll (nframes_t start, nframes_t end) request_play_range (&lar, true); } void -Session::request_roll_at_and_return (nframes_t start, nframes_t return_to) +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; @@ -1419,9 +1488,7 @@ Session::engine_halted () void Session::xrun_recovery () { - // can't cast away volatile so copy and emit that - nframes64_t tframe = _transport_frame; - Xrun (tframe); //EMIT SIGNAL + Xrun (_transport_frame); /* EMIT SIGNAL */ if (Config->get_stop_recording_on_xrun() && actively_recording()) { @@ -1440,100 +1507,70 @@ Session::route_processors_changed (RouteProcessorChange c) return; } - update_latency_compensation (false, false); + update_latency_compensation (); resort_routes (); + + set_dirty (); } void -Session::update_latency_compensation (bool with_stop, bool abort) +Session::allow_auto_play (bool yn) { - bool update_jack = false; - PostTransportWork ptw; - - if (_state_of_the_state & Deletion) { - return; - } - - _worst_track_latency = 0; - ptw = post_transport_work(); - -#undef DEBUG_LATENCY -#ifdef DEBUG_LATENCY - cerr << "\n---------------------------------\nUPDATE LATENCY\n"; -#endif - - boost::shared_ptr r = routes.reader (); - - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - - if (with_stop) { - (*i)->handle_transport_stopped (abort, (ptw & PostTransportLocate), (!(ptw & PostTransportLocate) || pending_locate_flush)); - } - - nframes_t old_latency = (*i)->output()->signal_latency (); - nframes_t track_latency = (*i)->update_total_latency (); - - if (old_latency != track_latency) { - (*i)->input()->update_port_total_latencies (); - (*i)->output()->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 (); - } + auto_play_legal = yn; +} -#ifdef DEBUG_LATENCY - cerr << "\tworst was " << _worst_track_latency << endl; -#endif +void +Session::reset_jack_connection (jack_client_t* jack) +{ + JACK_Slave* js; - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - (*i)->set_latency_delay (_worst_track_latency); + if (_slave && ((js = dynamic_cast (_slave)) != 0)) { + js->reset_client (jack); } +} - set_worst_io_latencies (); - - /* reflect any changes in latencies into capture offsets - */ - - boost::shared_ptr dsl = diskstreams.reader(); - - for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) { - (*i)->set_capture_offset (); +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; } void -Session::allow_auto_play (bool yn) +Session::send_mmc_locate (framepos_t t) { - auto_play_legal = yn; + Timecode::Time time; + timecode_time_subframes (t, time); + MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (time)); } +/** 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::reset_jack_connection (jack_client_t* jack) +Session::request_suspend_timecode_transmission () { - JACK_Slave* js; + SessionEvent* ev = new SessionEvent (SessionEvent::SetTimecodeTransmission, SessionEvent::Add, SessionEvent::Immediate, 0, 0, false); + queue_event (ev); +} - if (_slave && ((js = dynamic_cast (_slave)) != 0)) { - js->reset_client (jack); - } +void +Session::request_resume_timecode_transmission () +{ + SessionEvent* ev = new SessionEvent (SessionEvent::SetTimecodeTransmission, SessionEvent::Add, SessionEvent::Immediate, 0, 0, true); + queue_event (ev); } bool -Session::maybe_stop (nframes_t limit) +Session::timecode_transmission_suspended () const { - 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; + return g_atomic_int_get (&_suspend_timecode_transmission) == 1; }