Fix thinko in 9581cb26 - scratch-buffer can't be used recursively.
[ardour.git] / libs / ardour / session_transport.cc
index 0c5e3cbff512a4ca03c5a692939ade6744c37ba9..65a9748ce60ebfe6187f533bac973cc2e140621d 100644 (file)
@@ -37,6 +37,7 @@
 
 #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/scene_changer.h"
 #include "ardour/session.h"
 #include "ardour/slave.h"
+#include "ardour/tempo.h"
 #include "ardour/operations.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace ARDOUR;
@@ -109,7 +111,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; // as_default
+       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);
 }
@@ -160,9 +162,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 +334,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) {
@@ -286,7 +363,7 @@ Session::realtime_stop (bool abort, bool clear_state)
        if (clear_state && !Config->get_loop_is_mode()) {
                unset_play_loop ();
        }
-       
+
        reset_slave_state ();
 
        _transport_speed = 0;
@@ -321,7 +398,7 @@ Session::butler_transport_work ()
        PostTransportWork ptw;
        boost::shared_ptr<RouteList> r = routes.reader ();
        uint64_t before;
-       
+
        int on_entry = g_atomic_int_get (&_butler->should_do_transport_work);
        finished = true;
        ptw = post_transport_work();
@@ -334,7 +411,7 @@ Session::butler_transport_work ()
                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.
                         *
@@ -367,6 +444,12 @@ Session::butler_transport_work ()
        }
 
        if (ptw & PostTransportAdjustPlaybackBuffering) {
+               /* non_realtime_locate() calls Automatable::transport_located()
+                * for every route. This eventually calls
+                * ARDOUR::AutomationList::state () which has a LocaleGuard,
+                * and would switch locales forth/back every time.
+                */
+               LocaleGuard lg;
                for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                        boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
                        if (tr) {
@@ -415,7 +498,7 @@ Session::butler_transport_work ()
                /* don't seek if locate will take care of that in non_realtime_stop() */
 
                if (!(ptw & PostTransportLocate)) {
-
+                       LocaleGuard lg; // see note for non_realtime_locate() above
                        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                                (*i)->non_realtime_locate (_transport_frame);
 
@@ -517,16 +600,19 @@ Session::non_realtime_locate ()
                } else if (loc) {
                        set_track_loop (false);
                }
-               
+
        } else {
 
                /* no more looping .. should have been noticed elsewhere */
        }
 
-       
-       boost::shared_ptr<RouteList> rl = routes.reader();
-       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-               (*i)->non_realtime_locate (_transport_frame);
+
+       {
+               LocaleGuard lg; // see note for non_realtime_locate() above
+               boost::shared_ptr<RouteList> rl = routes.reader();
+               for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+                       (*i)->non_realtime_locate (_transport_frame);
+               }
        }
 
        _scene_changer->locate (_transport_frame);
@@ -555,7 +641,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.
 
@@ -569,7 +655,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;
@@ -583,13 +669,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();
 
@@ -600,7 +686,7 @@ Session::select_playhead_priority_target (framepos_t& jump_to)
                        }
                }
        }
-       
+
        if (jump_to < 0 && (autoreturn & RegionSelectionStart)) {
                if (!_object_selection.empty()) {
                        jump_to = _object_selection.from;
@@ -610,7 +696,7 @@ Session::select_playhead_priority_target (framepos_t& jump_to)
        if (jump_to < 0 && (autoreturn & LastLocate)) {
                jump_to = _last_roll_location;
        }
-       
+
        return jump_to >= 0;
 }
 #else
@@ -618,7 +704,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;
        }
 
@@ -731,6 +817,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)) {
 
@@ -770,6 +860,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.
        */
@@ -783,15 +874,18 @@ 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);
+       {
+               LocaleGuard lg; // see note for non_realtime_locate() above
+               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;
+                       }
                }
        }
 
@@ -804,14 +898,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<MTC_Slave*>(_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);
@@ -853,6 +947,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 */
 
@@ -904,8 +999,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);
@@ -957,7 +1052,7 @@ Session::set_play_loop (bool yn, double speed)
 
                play_loop = true;
                have_looped = false;
-               
+
                if (loc) {
 
                        unset_play_range ();
@@ -1094,7 +1189,7 @@ void
 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
@@ -1105,7 +1200,7 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool
 
        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,
@@ -1138,12 +1233,9 @@ Session::locate (framepos_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;
        _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:
         *
@@ -1155,7 +1247,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) &&
            (!Profile->get_trx() || !(config.get_external_sync() && !synced_to_engine()))) {
                realtime_stop (false, true); // XXX paul - check if the 2nd arg is really correct
@@ -1175,7 +1267,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 */
@@ -1215,7 +1307,7 @@ 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 {
@@ -1246,11 +1338,11 @@ Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool
                                }
 
                                boost::shared_ptr<RouteList> rl = routes.reader();
-                               
+
                                for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
                                        boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
-                                       
-                                       if (tr && tr->record_enabled ()) {
+
+                                       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);
                                        }
@@ -1275,7 +1367,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.
@@ -1321,13 +1415,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 ();
@@ -1342,13 +1437,15 @@ Session::set_transport_speed (double speed, framepos_t destination_frame, bool a
                }
 
        } 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()) {
 
@@ -1371,6 +1468,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 ();
                }
@@ -1463,12 +1561,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 */
@@ -1484,10 +1583,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
@@ -1497,16 +1596,16 @@ 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.
@@ -1515,15 +1614,15 @@ Session::stop_transport (bool abort, bool clear_state)
                           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;
@@ -1533,9 +1632,9 @@ Session::stop_transport (bool abort, bool clear_state)
        } 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.
                */
 
@@ -1561,7 +1660,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;
@@ -1595,6 +1694,40 @@ Session::start_transport ()
                if (!dynamic_cast<MTC_Slave*>(_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));
@@ -1625,6 +1758,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;
@@ -1709,7 +1843,7 @@ 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;
 
@@ -1975,7 +2109,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;
        }