X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession_transport.cc;h=42ef46ca257e34f7a76ef3206050dc1b780a6328;hb=3a65005a75851c9a41b694babd51f5054972e5dc;hp=6954bd0288cf742e11900541bbd70eebc9fd8cac;hpb=aa0effb4cb38f4c3a06564bd9e6a0ee516d4f958;p=ardour.git diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 6954bd0288..42ef46ca25 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -314,12 +314,13 @@ Session::butler_transport_work () bool finished; PostTransportWork ptw; boost::shared_ptr r = routes.reader (); - + uint64_t before; + 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))); + DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler transport work, todo = %1 at %2\n", enum_2_string (ptw), (before = g_get_monotonic_time()))); if (ptw & PostTransportAdjustPlaybackBuffering) { for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { @@ -384,6 +385,7 @@ Session::butler_transport_work () } if (ptw & PostTransportLocate) { + DEBUG_TRACE (DEBUG::Transport, "nonrealtime locate invoked from BTW\n"); non_realtime_locate (); } @@ -409,7 +411,7 @@ Session::butler_transport_work () g_atomic_int_dec_and_test (&_butler->should_do_transport_work); - DEBUG_TRACE (DEBUG::Transport, X_("Butler transport work all done\n")); + DEBUG_TRACE (DEBUG::Transport, string_compose (X_("Butler transport work all done after %1 usecs\n"), g_get_monotonic_time() - before)); DEBUG_TRACE (DEBUG::Transport, X_(string_compose ("Frame %1\n", _transport_frame))); } @@ -445,6 +447,39 @@ Session::non_realtime_overwrite (int on_entry, bool& finished) void Session::non_realtime_locate () { + DEBUG_TRACE (DEBUG::Transport, string_compose ("locate tracks to %1\n", _transport_frame)); + + if (Config->get_loop_is_mode() && get_play_loop()) { + + Location *loc = _locations->auto_loop_location(); + + if (!loc || (_transport_frame < loc->start() || _transport_frame >= loc->end())) { + /* jumped out of loop range: stop tracks from looping, + but leave loop (mode) enabled. + */ + set_track_loop (false); + + } else if (loc && Config->get_seamless_loop() && + ((loc->start() <= _transport_frame) || + (loc->end() > _transport_frame) ) ) { + + /* jumping to start of loop. This might have been done before but it is + * idempotent and cheap. Doing it here ensures that when we start playback + * outside the loop we still flip tracks into the magic seamless mode + * when needed. + */ + set_track_loop (true); + + } else if (loc) { + set_track_loop (false); + } + + } else { + + /* no more looping .. should have been noticed elsewhere */ + } + + boost::shared_ptr rl = routes.reader(); for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { (*i)->non_realtime_locate (_transport_frame); @@ -459,6 +494,100 @@ Session::non_realtime_locate () clear_clicks (); } +bool +Session::select_playhead_priority_target (framepos_t& jump_to) +{ + jump_to = -1; + + AutoReturnTarget autoreturn = Config->get_auto_return_target_list (); + + if (!autoreturn) { + return false; + } + + if (Profile->get_trx() && transport_rolling() ) { + // We're playing, so do nothing. + // Next stop will put us where we need to be. + return false; + } + + /* Note that the order of checking each AutoReturnTarget flag defines + the priority each flag. + + Ardour/Mixbus: Last Locate + Range Selection + Loop Range + Region Selection + + Tracks: Range Selection + Loop Range + Region Selection + Last Locate + */ + +#ifndef USE_TRACKS_CODE_FEATURES + if (autoreturn & LastLocate) { + jump_to = _last_roll_location; + } + + if (jump_to < 0 && (autoreturn & RangeSelectionStart)) { +#else + if (autoreturn & RangeSelectionStart) { +#endif + if (!_range_selection.empty()) { + jump_to = _range_selection.from; + } else { + if (transport_rolling ()) { + /* Range selection no longer exists, but we're playing, + so do nothing. Next stop will put us where + we need to be. + */ + return false; + } + } + } + + if (jump_to < 0 && (autoreturn & Loop)) { + /* don't try to handle loop play when synced to JACK */ + + if (!synced_to_engine()) { + Location *location = _locations->auto_loop_location(); + + if (location) { + jump_to = location->start(); + + if (Config->get_seamless_loop()) { + /* need to get track buffers reloaded */ + set_track_loop (true); + } + } + } + } + + if (jump_to < 0 && (autoreturn & RegionSelectionStart)) { + if (!_object_selection.empty()) { + jump_to = _object_selection.from; + } + } + +#ifdef USE_TRACKS_CODE_FEATURES + if (jump_to < 0 && (autoreturn & LastLocate)) { + jump_to = _last_roll_location; + } +#endif + + return jump_to >= 0; +} + +void +Session::follow_playhead_priority () +{ + framepos_t target; + + if (select_playhead_priority_target (target)) { + request_locate (target); + } +} void Session::non_realtime_stop (bool abort, int on_entry, bool& finished) @@ -542,8 +671,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) update_latency_compensation (); } - bool const auto_return_enabled = - (!config.get_external_sync() && (config.get_auto_return() || abort)); + bool const auto_return_enabled = (!config.get_external_sync() && (Config->get_auto_return_target_list() || abort)); if (auto_return_enabled || (ptw & PostTransportLocate) || @@ -569,40 +697,13 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) 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_engine()) { - - 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) { + framepos_t jump_to; - /* return to start of range */ + if (select_playhead_priority_target (jump_to)) { - if (!current_audio_range.empty()) { - _transport_frame = current_audio_range.front().start; - do_locate = true; - } - - } else { - - /* regular auto-return */ + _transport_frame = jump_to; + do_locate = true; - _transport_frame = _last_roll_location; - do_locate = true; - } } else if (abort) { _transport_frame = _last_roll_location; @@ -656,7 +757,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) _send_timecode_update = true; if (!dynamic_cast(_slave)) { - _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop)); + send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop)); /* This (::non_realtime_stop()) gets called by main process thread, which will lead to confusion @@ -677,9 +778,8 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) * save state only if there's no slave or if it's not yet locked. */ if (!_slave || !_slave->locked()) { - DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: pending save\n")); - /* capture start has been changed, so save pending state */ - save_state ("", true); + DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: requests save\n")); + SaveSessionRequested (_current_snapshot_name); saved = true; } } @@ -691,7 +791,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) /* save the current state of things if appropriate */ if (did_record && !saved) { - save_state (_current_snapshot_name); + SaveSessionRequested (_current_snapshot_name); } if (ptw & PostTransportStop) { @@ -750,16 +850,36 @@ Session::check_declick_out () void Session::unset_play_loop () { - play_loop = false; - clear_events (SessionEvent::AutoLoop); - clear_events (SessionEvent::AutoLoopDeclick); + if (play_loop) { + play_loop = false; + clear_events (SessionEvent::AutoLoop); + clear_events (SessionEvent::AutoLoopDeclick); + set_track_loop (false); + + + if (Config->get_seamless_loop()) { + /* likely need to flush track buffers: this will locate us to wherever we are */ + add_post_transport_work (PostTransportLocate); + _butler->schedule_transport_work (); + } + } +} + +void +Session::set_track_loop (bool yn) +{ + Location* loc = _locations->auto_loop_location (); + + if (!loc) { + yn = false; + } - // 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); + tr->set_loop (yn ? loc : 0); } } } @@ -787,30 +907,24 @@ Session::set_play_loop (bool yn, double speed) if (yn) { play_loop = true; - + have_looped = false; + if (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 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); - } + if (!Config->get_loop_is_mode()) { + /* set all tracks to use internal looping */ + set_track_loop (true); + } else { + /* we will do this in the locate to the start OR when we hit the end + * of the loop for the first time + */ } + } else { + /* set all tracks to NOT use internal looping */ + set_track_loop (false); } /* Put the delick and loop events in into the event list. The declick event will @@ -930,6 +1044,8 @@ Session::micro_locate (framecnt_t distance) void Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool for_seamless_loop, bool force, bool with_mmc) { + bool need_butler = false; + /* Locates for seamless looping are fairly different from other * locates. They assume that the diskstream buffers for each track * already have the correct data in them, and thus there is no need to @@ -938,6 +1054,9 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool * changes in the value of _transport_frame. */ + DEBUG_TRACE (DEBUG::Transport, string_compose ("rt-locate to %1, roll %2 flush %3 seamless %4 force %5 mmc %6\n", + target_frame, with_roll, with_flush, for_seamless_loop, force, with_mmc)); + if (actively_recording() && !for_seamless_loop) { return; } @@ -985,7 +1104,8 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool bool transport_was_stopped = !transport_rolling(); - if (transport_was_stopped && (!auto_play_legal || !config.get_auto_play()) && !with_roll && !(synced_to_engine() && play_loop)) { + if (transport_was_stopped && (!auto_play_legal || !config.get_auto_play()) && !with_roll && !(synced_to_engine() && play_loop) && + (!Profile->get_trx() || !(config.get_external_sync() && !synced_to_engine()))) { realtime_stop (false, true); // XXX paul - check if the 2nd arg is really correct transport_was_stopped = true; } else { @@ -1002,8 +1122,8 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool } add_post_transport_work (todo); - _butler->schedule_transport_work (); - + need_butler = true; + } else { /* this is functionally what clear_clicks() does but with a tentative lock */ @@ -1042,8 +1162,18 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool // located outside the loop: cancel looping directly, this is called from event handling context + have_looped = false; + if (!Config->get_loop_is_mode()) { set_play_loop (false, _transport_speed); + } else { + if (Config->get_seamless_loop()) { + /* this will make the non_realtime_locate() in the butler + which then causes seek() in tracks actually do the right + thing. + */ + set_track_loop (false); + } } } else if (_transport_frame == al->start()) { @@ -1052,6 +1182,19 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool if (for_seamless_loop) { + if (!have_looped) { + /* first time */ + if (_last_roll_location != al->start()) { + /* didn't start at loop start - playback must have + * started before loop since we've now hit the loop + * end. + */ + add_post_transport_work (PostTransportLocate); + need_butler = true; + } + + } + // this is only necessary for seamless looping boost::shared_ptr rl = routes.reader(); @@ -1072,6 +1215,10 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool } } + if (need_butler) { + _butler->schedule_transport_work (); + } + loop_changing = false; _send_timecode_update = true; @@ -1138,7 +1285,7 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a } _engine.transport_stop (); } else { - bool const auto_return_enabled = (!config.get_external_sync() && (config.get_auto_return() || abort)); + bool const auto_return_enabled = (!config.get_external_sync() && (Config->get_auto_return_target_list() || abort)); if (!auto_return_enabled) { _requested_return_frame = destination_frame; @@ -1161,7 +1308,14 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a if (location != 0) { if (_transport_frame != location->start()) { + + if (Config->get_seamless_loop()) { + /* force tracks to do their thing */ + set_track_loop (true); + } + /* jump to start and then roll from there */ + request_locate (location->start(), true); return; } @@ -1236,7 +1390,28 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a } DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC3 with speed = %1\n", _transport_speed)); - TransportStateChange (); /* EMIT SIGNAL */ + + /* throttle signal emissions. + * when slaved [_last]_transport_speed + * usually changes every cycle (tiny amounts due to DLL). + * Emitting a signal every cycle is overkill and unwarranted. + * + * Using _last_transport_speed is not acceptable, + * since it allows for large changes over a long period + * of time. Hence we introduce a dedicated variable to keep track + * + * The 0.2% dead-zone is somewhat arbitrary. Main use-case + * for TransportStateChange() here is the ShuttleControl display. + */ + if (fabsf(_signalled_varispeed - speed) > .002f + // still, signal hard changes to 1.0 and 0.0: + || ( speed == 1.f && _signalled_varispeed != 1.f) + || ( speed == 0.f && _signalled_varispeed != 0.f) + ) + { + TransportStateChange (); /* EMIT SIGNAL */ + _signalled_varispeed = speed; + } } } @@ -1371,7 +1546,7 @@ Session::start_transport () Timecode::Time time; timecode_time_subframes (_transport_frame, time); if (!dynamic_cast(_slave)) { - _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); + send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); } } @@ -1436,6 +1611,13 @@ Session::reset_rf_scale (framecnt_t motion) } } +void +Session::mtc_status_changed (bool yn) +{ + g_atomic_int_set (&_mtc_active, yn); + MTCSyncStateChanged( yn ); +} + void Session::use_sync_source (Slave* new_slave) { @@ -1448,6 +1630,18 @@ Session::use_sync_source (Slave* new_slave) delete _slave; _slave = new_slave; + MTC_Slave* mtc_slave = dynamic_cast(_slave); + if (mtc_slave) { + mtc_slave->ActiveChanged.connect_same_thread (mtc_status_connection, boost::bind (&Session::mtc_status_changed, this, _1)); + MTCSyncStateChanged(mtc_slave->locked() ); + } else { + if (g_atomic_int_get (&_mtc_active) ){ + g_atomic_int_set (&_mtc_active, 0); + MTCSyncStateChanged( false ); + } + mtc_status_connection.disconnect (); + } + DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", _slave)); // need to queue this for next process() cycle @@ -1683,8 +1877,6 @@ Session::engine_halted () */ if (_butler) { - g_atomic_int_set (&_butler->should_do_transport_work, 0); - set_post_transport_work (PostTransportWork (0)); _butler->stop (); } @@ -1700,6 +1892,8 @@ Session::engine_halted () void Session::xrun_recovery () { + ++_xrun_count; + Xrun (_transport_frame); /* EMIT SIGNAL */ if (Config->get_stop_recording_on_xrun() && actively_recording()) { @@ -1720,6 +1914,12 @@ Session::route_processors_changed (RouteProcessorChange c) } if (c.type == RouteProcessorChange::MeterPointChange) { + set_dirty (); + return; + } + + if (c.type == RouteProcessorChange::RealTimeChange) { + set_dirty (); return; } @@ -1759,7 +1959,7 @@ Session::send_mmc_locate (framepos_t t) if (!_engine.freewheeling()) { Timecode::Time time; timecode_time_subframes (t, time); - _mmc->send (MIDI::MachineControlCommand (time)); + send_immediate_mmc (MIDI::MachineControlCommand (time)); } }