X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession_transport.cc;h=f09f3ac2538d2e2bc60571ac646901472d793fbb;hb=eeeb8563c28da3f84467fbd0213684077a00bfaa;hp=51734a06c0ba4a280e0b737556a20d4095eba86f;hpb=46c83693284ece4a732d26e62113ea4ac584d539;p=ardour.git diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 51734a06c0..f09f3ac253 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -37,17 +37,22 @@ #include "ardour/audioengine.h" #include "ardour/auditioner.h" +#include "ardour/automation_watch.h" #include "ardour/butler.h" #include "ardour/click.h" #include "ardour/debug.h" +#include "ardour/disk_reader.h" #include "ardour/location.h" #include "ardour/profile.h" #include "ardour/scene_changer.h" #include "ardour/session.h" #include "ardour/slave.h" +#include "ardour/tempo.h" #include "ardour/operations.h" +#include "ardour/vca.h" +#include "ardour/vca_manager.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace std; using namespace ARDOUR; @@ -72,15 +77,6 @@ Session::add_post_transport_work (PostTransportWork ptw) error << "Could not set post transport work! Crazy thread madness, call the programmers" << endmsg; } -void -Session::request_input_change_handling () -{ - if (!(_state_of_the_state & (InitialConnecting|Deletion))) { - SessionEvent* ev = new SessionEvent (SessionEvent::InputConfigurationChange, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0); - queue_event (ev); - } -} - void Session::request_sync_source (Slave* new_slave) { @@ -109,7 +105,7 @@ void Session::request_transport_speed (double speed, bool as_default) { SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, speed); - ev->third_yes_or_no = true; + ev->third_yes_or_no = as_default; // as_default DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport speed = %1 as default = %2\n", speed, as_default)); queue_event (ev); } @@ -128,14 +124,6 @@ Session::request_transport_speed_nonzero (double speed, bool as_default) request_transport_speed (speed, as_default); } -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); -} - void Session::request_stop (bool abort, bool clear_state) { @@ -160,9 +148,84 @@ Session::force_locate (framepos_t target_frame, bool with_roll) queue_event (ev); } +void +Session::unset_preroll_record_punch () +{ + if (_preroll_record_punch_pos >= 0) { + remove_event (_preroll_record_punch_pos, SessionEvent::RecordStart); + } + _preroll_record_punch_pos = -1; +} + +void +Session::unset_preroll_record_trim () +{ + _preroll_record_trim_len = 0; +} + +void +Session::request_preroll_record_punch (framepos_t rec_in, framecnt_t preroll) +{ + if (actively_recording ()) { + return; + } + unset_preroll_record_punch (); + unset_preroll_record_trim (); + framepos_t start = std::max ((framepos_t)0, rec_in - preroll); + + _preroll_record_punch_pos = rec_in; + if (_preroll_record_punch_pos >= 0) { + replace_event (SessionEvent::RecordStart, _preroll_record_punch_pos); + config.set_punch_in (false); + config.set_punch_out (false); + } + maybe_enable_record (); + request_locate (start, true); + set_requested_return_frame (rec_in); +} + +void +Session::request_preroll_record_trim (framepos_t rec_in, framecnt_t preroll) +{ + if (actively_recording ()) { + return; + } + unset_preroll_record_punch (); + unset_preroll_record_trim (); + + config.set_punch_in (false); + config.set_punch_out (false); + + framepos_t pos = std::max ((framepos_t)0, rec_in - preroll); + _preroll_record_trim_len = preroll; + maybe_enable_record (); + request_locate (pos, true); + set_requested_return_frame (rec_in); +} + +void +Session::request_count_in_record () +{ + if (actively_recording ()) { + return; + } + if (transport_rolling()) { + return; + } + maybe_enable_record (); + _count_in_once = true; + request_transport_speed (1.0, true); +} + void Session::request_play_loop (bool yn, bool change_transport_roll) { + if (_slave && yn) { + // don't attempt to loop when not using Internal Transport + // see also gtk2_ardour/ardour_ui_options.cc parameter_changed() + return; + } + SessionEvent* ev; Location *location = _locations->auto_loop_location(); double target_speed; @@ -257,11 +320,11 @@ Session::realtime_stop (bool abort, bool clear_state) for (RouteList::iterator i = r->begin (); i != r->end(); ++i) { (*i)->realtime_handle_transport_stopped (); } - + DEBUG_TRACE (DEBUG::Transport, string_compose ("stop complete, auto-return scheduled for return to %1\n", _requested_return_frame)); /* the duration change is not guaranteed to have happened, but is likely */ - + todo = PostTransportWork (todo | PostTransportDuration); if (abort) { @@ -283,6 +346,10 @@ Session::realtime_stop (bool abort, bool clear_state) /* 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)); + if (clear_state && !Config->get_loop_is_mode()) { + unset_play_loop (); + } + reset_slave_state (); _transport_speed = 0; @@ -310,18 +377,58 @@ Session::realtime_locate () void Session::butler_transport_work () { + /* Note: this function executes in the butler thread context */ + restart: 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 at %2\n", enum_2_string (ptw), (before = g_get_monotonic_time()))); + + if (ptw & PostTransportLocate) { + + if (get_play_loop() && !Config->get_seamless_loop()) { + + DEBUG_TRACE (DEBUG::Butler, "flush loop recording fragment to disk\n"); + + /* this locate might be happening while we are + * loop recording. + * + * Non-seamless looping will require a locate (below) that + * will reset capture buffers and throw away data. + * + * Rather than first find all tracks and see if they + * have outstanding data, just do a flush anyway. It + * may be cheaper this way anyway, and is certainly + * more accurate. + */ + + bool more_disk_io_to_do = false; + uint32_t errors = 0; + + do { + more_disk_io_to_do = _butler->flush_tracks_to_disk_after_locate (r, errors); + + if (errors) { + break; + } + + if (more_disk_io_to_do) { + continue; + } + + } while (false); + + } + } + if (ptw & PostTransportAdjustPlaybackBuffering) { for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); @@ -331,7 +438,10 @@ Session::butler_transport_work () } (*i)->non_realtime_locate (_transport_frame); } - + VCAList v = _vca_manager->vcas (); + for (VCAList::const_iterator i = v.begin(); i != v.end(); ++i) { + (*i)->non_realtime_locate (_transport_frame); + } } if (ptw & PostTransportAdjustCaptureBuffering) { @@ -349,15 +459,6 @@ Session::butler_transport_work () } } - if (ptw & PostTransportInputChange) { - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); - if (tr) { - tr->non_realtime_input_change (); - } - } - } - if (ptw & PostTransportSpeed) { non_realtime_set_speed (); } @@ -371,7 +472,6 @@ Session::butler_transport_work () /* don't seek if locate will take care of that in non_realtime_stop() */ if (!(ptw & PostTransportLocate)) { - for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { (*i)->non_realtime_locate (_transport_frame); @@ -381,6 +481,10 @@ Session::butler_transport_work () goto restart; } } + VCAList v = _vca_manager->vcas (); + for (VCAList::const_iterator i = v.begin(); i != v.end(); ++i) { + (*i)->non_realtime_locate (_transport_frame); + } } } @@ -411,8 +515,7 @@ Session::butler_transport_work () g_atomic_int_dec_and_test (&_butler->should_do_transport_work); - 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))); + DEBUG_TRACE (DEBUG::Transport, string_compose (X_("Butler transport work all done after %1 usecs @ %2 trw = %3\n"), g_get_monotonic_time() - before, _transport_frame, _butler->transport_work_requested())); } void @@ -422,7 +525,7 @@ Session::non_realtime_set_speed () 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 (); + tr->non_realtime_speed_change (); } } } @@ -473,18 +576,53 @@ Session::non_realtime_locate () } 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); + + microseconds_t begin = get_microseconds (); + framepos_t tf; + + { + boost::shared_ptr rl = routes.reader(); + + restart: + gint sc = g_atomic_int_get (&_seek_counter); + tf = _transport_frame; + + cerr << "\n\n >>> START Non-RT locate on routes to " << tf << " counter = " << sc << "\n\n"; + + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + (*i)->non_realtime_locate (tf); + //::usleep (250000); + cerr << "\t\tcounter after track: " << g_atomic_int_get (&_seek_counter) << endl; + if (sc != g_atomic_int_get (&_seek_counter)) { + cerr << "\n\n RESTART locate, new seek delivered\n"; + goto restart; + } + } + + cerr << "\n\n <<< DONE Non-RT locate on routes\n\n"; + } + + { + /* VCAs are quick to locate because they have no data (except + automation) associated with them. Don't bother with a + restart mechanism here, but do use the same transport frame + that the Routes used. + */ + VCAList v = _vca_manager->vcas (); + for (VCAList::const_iterator i = v.begin(); i != v.end(); ++i) { + (*i)->non_realtime_locate (tf); + } } + microseconds_t end = get_microseconds (); + cerr << "Locate took " << setprecision (3) << ((end - begin) /1000000.0) << " secs\n"; + _scene_changer->locate (_transport_frame); /* XXX: it would be nice to generate the new clicks here (in the non-RT thread) @@ -511,7 +649,7 @@ Session::select_playhead_priority_target (framepos_t& jump_to) // 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. @@ -525,7 +663,7 @@ Session::select_playhead_priority_target (framepos_t& jump_to) Region Selection Last Locate */ - + if (autoreturn & RangeSelectionStart) { if (!_range_selection.empty()) { jump_to = _range_selection.from; @@ -539,13 +677,13 @@ Session::select_playhead_priority_target (framepos_t& jump_to) } } } - + if (jump_to < 0 && (autoreturn & Loop) && get_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) { jump_to = location->start(); @@ -553,20 +691,20 @@ Session::select_playhead_priority_target (framepos_t& jump_to) /* 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; } - } + } if (jump_to < 0 && (autoreturn & LastLocate)) { jump_to = _last_roll_location; } - + return jump_to >= 0; } #else @@ -574,7 +712,7 @@ Session::select_playhead_priority_target (framepos_t& jump_to) bool Session::select_playhead_priority_target (framepos_t& jump_to) { - if (!config.get_auto_return()) { + if (config.get_external_sync() || !config.get_auto_return()) { return false; } @@ -670,9 +808,15 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) if (_engine.running()) { 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)); + (*i)->non_realtime_transport_stop (_transport_frame, !(ptw & PostTransportLocate) || pending_locate_flush); + } + VCAList v = _vca_manager->vcas (); + for (VCAList::const_iterator i = v.begin(); i != v.end(); ++i) { + (*i)->non_realtime_transport_stop (_transport_frame, !(ptw & PostTransportLocate) || pending_locate_flush); } + update_latency_compensation (); } @@ -687,6 +831,10 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) flush_all_inserts (); } + // rg: what is the logic behind this case? + // _requested_return_frame should be ignored when synced_to_engine/slaved. + // currently worked around in MTC_Slave by forcing _requested_return_frame to -1 + // 2016-01-10 if ((auto_return_enabled || synced_to_engine() || _requested_return_frame >= 0) && !(ptw & PostTransportLocate)) { @@ -726,6 +874,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) } clear_clicks(); + unset_preroll_record_trim (); /* do this before seeking, because otherwise the tracks will do the wrong thing in seamless loop mode. */ @@ -739,15 +888,24 @@ 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 (RouteList::iterator i = r->begin(); i != r->end(); ++i) { - DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler PTW: locate on %1\n", (*i)->name())); - (*i)->non_realtime_locate (_transport_frame); + { + DEBUG_TRACE (DEBUG::Transport, X_("Butler PTW: locate\n")); + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + DEBUG_TRACE (DEBUG::Transport, string_compose ("Butler PTW: locate on %1\n", (*i)->name())); + (*i)->non_realtime_locate (_transport_frame); - if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) { - finished = false; - /* we will be back */ - return; + if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) { + finished = false; + /* we will be back */ + return; + } + } + } + + { + VCAList v = _vca_manager->vcas (); + for (VCAList::const_iterator i = v.begin(); i != v.end(); ++i) { + (*i)->non_realtime_locate (_transport_frame); } } @@ -760,14 +918,14 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) if (_engine.connected() && !_engine.freewheeling()) { // need to queue this in the next RT cycle _send_timecode_update = true; - + if (!dynamic_cast(_slave)) { send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop)); /* This (::non_realtime_stop()) gets called by main process thread, which will lead to confusion when calling AsyncMIDIPort::write(). - + Something must be done. XXX */ send_mmc_locate (_transport_frame); @@ -809,6 +967,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) PositionChanged (_transport_frame); /* EMIT SIGNAL */ DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC with speed = %1\n", _transport_speed)); TransportStateChange (); /* EMIT SIGNAL */ + AutomationWatch::instance().transport_stop_automation_watches (_transport_frame); /* and start it up again if relevant */ @@ -860,8 +1019,8 @@ Session::unset_play_loop () 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); @@ -883,7 +1042,7 @@ Session::set_track_loop (bool yn) 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 && !tr->is_private_route()) { tr->set_loop (yn ? loc : 0); } } @@ -913,7 +1072,7 @@ Session::set_play_loop (bool yn, double speed) play_loop = true; have_looped = false; - + if (loc) { unset_play_range (); @@ -923,8 +1082,8 @@ Session::set_play_loop (bool yn, double speed) /* 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 + /* we will do this in the locate to the start OR when we hit the end + * of the loop for the first time */ } } else { @@ -954,11 +1113,11 @@ Session::set_play_loop (bool yn, double speed) rolling, do not locate to loop start. */ if (!transport_rolling() && (speed != 0.0)) { - start_locate (loc->start(), true, true, false, Config->get_seamless_loop()); + start_locate (loc->start(), true, true, false, true); } } else { if (speed != 0.0) { - start_locate (loc->start(), true, true, false, Config->get_seamless_loop()); + start_locate (loc->start(), true, true, false, true); } } } @@ -982,7 +1141,7 @@ Session::flush_all_inserts () } void -Session::start_locate (framepos_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 for_loop_enabled, bool force) { if (target_frame < 0) { error << _("Locate called for negative sample position - ignored") << endmsg; @@ -1003,7 +1162,7 @@ Session::start_locate (framepos_t target_frame, bool with_roll, bool with_flush, will use the incorrect _transport_frame and report an old and incorrect time to Jack transport */ - locate (target_frame, with_roll, with_flush, with_loop, force); + locate (target_frame, with_roll, with_flush, for_loop_enabled, force); } /* tell JACK to change transport position, and we will @@ -1019,7 +1178,7 @@ Session::start_locate (framepos_t target_frame, bool with_roll, bool with_flush, } } else { - locate (target_frame, with_roll, with_flush, with_loop, force); + locate (target_frame, with_roll, with_flush, for_loop_enabled, force); } } @@ -1047,26 +1206,29 @@ Session::micro_locate (framecnt_t distance) /** @param with_mmc true to send a MMC locate command when the locate is done */ void -Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool for_seamless_loop, bool force, bool with_mmc) +Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool for_loop_enabled, 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 * actually tell the tracks to locate. What does need to be done, * though, is all the housekeeping that is associated with non-linear - * changes in the value of _transport_frame. + * 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; - } + DEBUG_TRACE (DEBUG::Transport, string_compose ("rt-locate to %1, roll %2 flush %3 loop-enabled %4 force %5 mmc %6\n", + target_frame, with_roll, with_flush, for_loop_enabled, force, with_mmc)); + + if (!force && _transport_frame == target_frame && !loop_changing && !for_loop_enabled) { + + /* already at the desired position. Not forced to locate, + the loop isn't changing, so unless we're told to + start rolling also, there's nothing to do but + tell the world where we are (again). + */ - if (!force && _transport_frame == target_frame && !loop_changing && !for_seamless_loop) { if (with_roll) { set_transport_speed (1.0, 0, false); } @@ -1075,7 +1237,7 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool return; } - if (_transport_speed && !for_seamless_loop) { + if (_transport_speed && !(for_loop_enabled && Config->get_seamless_loop())) { /* Schedule a declick. We'll be called again when its done. We only do it this way for ordinary locates, not those due to **seamless** loops. @@ -1086,17 +1248,20 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool pending_locate_frame = target_frame; pending_locate_roll = with_roll; pending_locate_flush = with_flush; + cerr << "Declick scheduled ... back soon\n"; return; } } + cerr << "... now doing the actual locate\n"; + // Update Timecode time - // [DR] FIXME: find out exactly where this should go below _transport_frame = target_frame; + // Bump seek counter so that any in-process locate in the butler + // thread(s?) can restart. + g_atomic_int_inc (&_seek_counter); _last_roll_or_reversal_location = 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: * @@ -1109,7 +1274,7 @@ 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; @@ -1118,7 +1283,7 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool realtime_locate (); } - if (force || !for_seamless_loop || loop_changing) { + if (force || !for_loop_enabled || loop_changing) { PostTransportWork todo = PostTransportLocate; @@ -1128,7 +1293,7 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool add_post_transport_work (todo); need_butler = true; - + } else { /* this is functionally what clear_clicks() does but with a tentative lock */ @@ -1163,12 +1328,12 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool Location* al = _locations->auto_loop_location(); if (al) { - if (_transport_frame < al->start() || _transport_frame > al->end()) { + if (_transport_frame < al->start() || _transport_frame >= al->end()) { // 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 { @@ -1180,37 +1345,32 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool set_track_loop (false); } } - + } else if (_transport_frame == al->start()) { // located to start of loop - this is looping, basically - 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; - } - + 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(); - - for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { - boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); - - if (tr && tr->record_enabled ()) { - // tell it we've looped, so it can deal with the record state - tr->transport_looped (_transport_frame); - } + + } + + 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->rec_enable_control()->get_value()) { + // tell it we've looped, so it can deal with the record state + tr->transport_looped (_transport_frame); } } @@ -1233,7 +1393,9 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool } _last_roll_location = _last_roll_or_reversal_location = _transport_frame; - Located (); /* EMIT SIGNAL */ + if (!synced_to_engine () || _transport_frame == _engine.transport_frame ()) { + Located (); /* EMIT SIGNAL */ + } } /** Set the transport speed. @@ -1243,7 +1405,7 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool void Session::set_transport_speed (double speed, framepos_t destination_frame, bool abort, bool clear_state, bool as_default) { - DEBUG_TRACE (DEBUG::Transport, string_compose ("@ %5 Set transport speed to %1, abort = %2 clear_state = %3, current = %4 as_default %6\n", + DEBUG_TRACE (DEBUG::Transport, string_compose ("@ %5 Set transport speed to %1, abort = %2 clear_state = %3, current = %4 as_default %6\n", speed, abort, clear_state, _transport_speed, _transport_frame, as_default)); if (_transport_speed == speed) { @@ -1255,7 +1417,7 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a if (actively_recording() && speed != 1.0 && speed != 0.0) { /* no varispeed during recording */ - DEBUG_TRACE (DEBUG::Transport, string_compose ("No varispeed during recording cur_speed %1, frame %2\n", + DEBUG_TRACE (DEBUG::Transport, string_compose ("No varispeed during recording cur_speed %1, frame %2\n", _transport_speed, _transport_frame)); return; } @@ -1279,13 +1441,14 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a if (Config->get_monitoring_model() == HardwareMonitoring) { set_track_monitor_input_status (true); } - + if (synced_to_engine ()) { if (clear_state) { /* do this here because our response to the slave won't take care of it. */ _play_range = false; + _count_in_once = false; unset_play_loop (); } _engine.transport_stop (); @@ -1299,18 +1462,16 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a stop_transport (abort); } - if (!Config->get_loop_is_mode()) { - unset_play_loop (); - } - } else if (transport_stopped() && speed == 1.0) { - + if (as_default) { + _default_transport_speed = speed; + } /* we are stopped and we want to start rolling at speed 1 */ if (Config->get_loop_is_mode() && play_loop) { Location *location = _locations->auto_loop_location(); - + if (location != 0) { if (_transport_frame != location->start()) { @@ -1333,6 +1494,7 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a if (synced_to_engine()) { _engine.transport_start (); + _count_in_once = false; } else { start_transport (); } @@ -1369,7 +1531,7 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a 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)) { + if ((_transport_speed && speed * _transport_speed < 0.0) || (_last_transport_speed * speed < 0.0) || (_last_transport_speed == 0.0 && speed < 0.0)) { todo = PostTransportWork (todo | PostTransportReverse); _last_roll_or_reversal_location = _transport_frame; } @@ -1384,7 +1546,7 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a 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)) { + if (tr && tr->realtime_speed_change()) { todo = PostTransportWork (todo | PostTransportSpeed); } } @@ -1396,22 +1558,22 @@ 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)); - /* throttle signal emissions. + /* 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 + * 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 + if (fabs (_signalled_varispeed - speed) > .002 // still, signal hard changes to 1.0 and 0.0: - || ( speed == 1.f && _signalled_varispeed != 1.f) - || ( speed == 0.f && _signalled_varispeed != 0.f) + || ( speed == 1.0 && _signalled_varispeed != 1.0) + || ( speed == 0.0 && _signalled_varispeed != 0.0) ) { TransportStateChange (); /* EMIT SIGNAL */ @@ -1425,10 +1587,13 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a void Session::stop_transport (bool abort, bool clear_state) { + _count_in_once = false; if (_transport_speed == 0.0f) { return; } + DEBUG_TRACE (DEBUG::Transport, string_compose ("stop_transport, declick required? %1\n", get_transport_declick_required())); + if (!get_transport_declick_required()) { /* stop has not yet been scheduled */ @@ -1444,10 +1609,10 @@ Session::stop_transport (bool abort, bool clear_state) } SubState new_bits; - + if (actively_recording() && /* we are recording */ worst_input_latency() > current_block_size) { /* input latency exceeds block size, so simple 1 cycle delay before stop is not enough */ - + /* we need to capture the audio that is still somewhere in the pipeline between wherever it was generated and the process callback. This means that even though the user (or something else) has asked us to stop, we have to roll @@ -1457,33 +1622,33 @@ Session::stop_transport (bool abort, bool clear_state) we still need playback to "stop" now, however, which is why we schedule a declick below. */ - + DEBUG_TRACE (DEBUG::Transport, string_compose ("stop transport requested @ %1, scheduled for + %2 = %3, abort = %4\n", _transport_frame, _worst_input_latency, _transport_frame + _worst_input_latency, abort)); - + SessionEvent *ev = new SessionEvent (SessionEvent::StopOnce, SessionEvent::Replace, _transport_frame + _worst_input_latency, 0, 0, abort); - + merge_event (ev); /* request a declick at the start of the next process cycle() so that playback ceases. - It will remain silent until we actually stop (at the StopOnce event somewhere in + It will remain silent until we actually stop (at the StopOnce event somewhere in the future). The extra flag (StopPendingCapture) is set to ensure that check_declick_out() does not stop the transport too early. */ new_bits = SubState (PendingDeclickOut|StopPendingCapture); - + } else { - + /* Not recording, schedule a declick in the next process() cycle and then stop at its end */ - + new_bits = PendingDeclickOut; + DEBUG_TRACE (DEBUG::Transport, string_compose ("stop scheduled for next process cycle @ %1\n", _transport_frame)); } - /* we'll be called again after the declick */ transport_sub_state = SubState (transport_sub_state|new_bits); pending_abort = abort; @@ -1491,9 +1656,11 @@ Session::stop_transport (bool abort, bool clear_state) return; } else { - + + DEBUG_TRACE (DEBUG::Transport, "time to actually stop\n"); + /* declick was scheduled, but we've been called again, which means it is really time to stop - + XXX: we should probably split this off into its own method and call it explicitly. */ @@ -1519,7 +1686,7 @@ Session::start_transport () switch (record_status()) { case Enabled: - if (!config.get_punch_in()) { + if (!config.get_punch_in() && !preroll_record_punch_enabled()) { enable_record (); } break; @@ -1543,7 +1710,7 @@ Session::start_transport () 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); + tr->realtime_speed_change (); } } @@ -1553,6 +1720,40 @@ Session::start_transport () if (!dynamic_cast(_slave)) { send_immediate_mmc (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); } + + if (actively_recording() && click_data && (config.get_count_in () || _count_in_once)) { + _count_in_once = false; + /* calculate count-in duration (in audio samples) + * - use [fixed] tempo/meter at _transport_frame + * - calc duration of 1 bar + time-to-beat before or at transport_frame + */ + const Tempo& tempo = _tempo_map->tempo_at_frame (_transport_frame); + const Meter& meter = _tempo_map->meter_at_frame (_transport_frame); + + const double num = meter.divisions_per_bar (); + const double den = meter.note_divisor (); + const double barbeat = _tempo_map->exact_qn_at_frame (_transport_frame, 0) * den / (4. * num); + const double bar_fract = fmod (barbeat, 1.0); // fraction of bar elapsed. + + _count_in_samples = meter.frames_per_bar (tempo, _current_frame_rate); + + double dt = _count_in_samples / num; + if (bar_fract == 0) { + /* at bar boundary, count-in 2 bars before start. */ + _count_in_samples *= 2; + } else { + /* beats left after full bar until roll position */ + _count_in_samples *= 1. + bar_fract; + } + + int clickbeat = 0; + framepos_t cf = _transport_frame - _count_in_samples; + while (cf < _transport_frame) { + add_click (cf - _worst_track_latency, clickbeat == 0); + cf += dt; + clickbeat = fmod (clickbeat + 1, num); + } + } } DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC4 with speed = %1\n", _transport_speed)); @@ -1583,6 +1784,7 @@ Session::post_transport () if (ptw & PostTransportLocate) { if (((!config.get_external_sync() && (auto_play_legal && config.get_auto_play())) && !_exporting) || (ptw & PostTransportRoll)) { + _count_in_once = false; start_transport (); } else { transport_sub_state = 0; @@ -1642,6 +1844,12 @@ Session::use_sync_source (Slave* new_slave) delete _slave; _slave = new_slave; + + /* slave change, reset any DiskIO block on disk output because it is no + longer valid with a new slave. + */ + DiskReader::set_no_disk_output (false); + 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)); @@ -1667,15 +1875,15 @@ Session::use_sync_source (Slave* new_slave) } DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", _slave)); - + // need to queue this for next process() cycle _send_timecode_update = 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->hidden()) { - if (tr->realtime_set_speed (tr->speed(), true)) { + if (tr && !tr->is_private_route()) { + if (tr->realtime_speed_change()) { non_rt_required = true; } tr->set_slaved (_slave != 0); @@ -1767,16 +1975,6 @@ Session::switch_to_sync_source (SyncSource src) request_sync_source (new_slave); } -void -Session::set_track_speed (Track* track, double speed) -{ - if (track->realtime_set_speed (speed, false)) { - add_post_transport_work (PostTransportSpeed); - _butler->schedule_transport_work (); - set_dirty (); - } -} - void Session::unset_play_range () { @@ -1933,7 +2131,7 @@ Session::xrun_recovery () void Session::route_processors_changed (RouteProcessorChange c) { - if (ignore_route_processor_changes) { + if (g_atomic_int_get (&_ignore_route_processor_changes) > 0) { return; }