*/
-#include <cmath>
-#include <cerrno>
-#include <unistd.h>
-
#ifdef WAF_BUILD
#include "libardour-config.h"
#endif
+#include <cmath>
+#include <cerrno>
+#include <unistd.h>
#include "pbd/undo.h"
#include "pbd/error.h"
#include "midi++/port.h"
#include "midi++/manager.h"
-#include "ardour/ardour.h"
#include "ardour/audioengine.h"
#include "ardour/auditioner.h"
#include "ardour/butler.h"
_was_seamless = seamless;
ev->slave = new_slave;
+ DEBUG_TRACE (DEBUG::Slave, "sent request for new slave\n");
queue_event (ev);
}
void
-Session::request_transport_speed (double speed)
+Session::request_transport_speed (double speed, bool as_default)
{
SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Add, SessionEvent::Immediate, 0, speed);
- DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport speed = %1\n", speed));
+ ev->third_yes_or_no = true;
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("Request transport speed = %1 as default = %2\n", speed, as_default));
queue_event (ev);
}
* be used by callers who are varying transport speed but don't ever want to stop it.
*/
void
-Session::request_transport_speed_nonzero (double speed)
+Session::request_transport_speed_nonzero (double speed, bool as_default)
{
if (speed == 0) {
speed = DBL_EPSILON;
}
- request_transport_speed (speed);
+ request_transport_speed (speed, as_default);
}
void
if (tr) {
tr->adjust_playback_buffering ();
/* and refill those buffers ... */
- tr->non_realtime_locate (_transport_frame);
}
+ (*i)->non_realtime_locate (_transport_frame);
}
}
if (!(ptw & PostTransportLocate)) {
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (tr && !tr->hidden()) {
- tr->non_realtime_locate (_transport_frame);
- }
+ (*i)->non_realtime_locate (_transport_frame);
+
if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) {
/* new request, stop seeking, and start again */
g_atomic_int_dec_and_test (&_butler->should_do_transport_work);
{
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->non_realtime_locate (_transport_frame);
- }
+ (*i)->non_realtime_locate (_transport_frame);
}
/* XXX: it would be nice to generate the new clicks here (in the non-RT thread)
auditioner->cancel_audition ();
}
- clear_clicks();
cumulative_rf_motion = 0;
reset_rf_scale (0);
_requested_return_frame = -1;
if (do_locate) {
- clear_clicks ();
_engine.transport_locate (_transport_frame);
}
}
}
+ clear_clicks();
+
/* do this before seeking, because otherwise the tracks will do the wrong thing in seamless loop mode.
*/
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()));
- boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
- if (tr && !tr->hidden()) {
- tr->non_realtime_locate (_transport_frame);
- }
+ (*i)->non_realtime_locate (_transport_frame);
if (on_entry != g_atomic_int_get (&_butler->should_do_transport_work)) {
finished = false;
have_looped = false;
- send_full_time_code (_transport_frame);
-
- if (!dynamic_cast<MTC_Slave*>(_slave)) {
- MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop));
- send_mmc_locate (_transport_frame);
+ if (!_engine.freewheeling()) {
+ send_full_time_code (_transport_frame);
+
+ if (!dynamic_cast<MTC_Slave*>(_slave)) {
+ MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop));
+ send_mmc_locate (_transport_frame);
+ }
}
if ((ptw & PostTransportLocate) && get_record_enabled()) {
}
PositionChanged (_transport_frame); /* EMIT SIGNAL */
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC with speed = %1\n", _transport_speed));
TransportStateChange (); /* EMIT SIGNAL */
/* and start it up again if relevant */
if ((ptw & PostTransportLocate) && !config.get_external_sync() && pending_locate_roll) {
request_transport_speed (1.0);
- pending_locate_roll = false;
}
+
+ /* Even if we didn't do a pending locate roll this time, we don't want it hanging
+ around for next time.
+ */
+ pending_locate_roll = false;
}
void
/* this is called after a process() iteration. if PendingDeclickOut was set,
it means that we were waiting to declick the output (which has just been
- done) before doing something else. this is where we do that "something else".
+ done) before maybe doing something else. this is where we do that "something else".
note: called from the audio thread.
*/
stop_transport (pending_abort);
transport_sub_state &= ~(PendingDeclickOut|PendingLocate);
}
+
+ } else if (transport_sub_state & PendingLoopDeclickOut) {
+ /* Nothing else to do here; we've declicked, and the loop event will be along shortly */
+ transport_sub_state &= ~PendingLoopDeclickOut;
}
}
{
play_loop = false;
clear_events (SessionEvent::AutoLoop);
+ clear_events (SessionEvent::AutoLoopDeclick);
// set all tracks to NOT use internal looping
boost::shared_ptr<RouteList> rl = routes.reader ();
}
}
- /* put the loop event into the event list */
+ /* Put the delick and loop events in into the event list. The declick event will
+ cause a de-clicking fade-out just before the end of the loop, and it will also result
+ in a fade-in when the loop restarts. The AutoLoop event will peform the actual loop.
+ */
- SessionEvent* event = new SessionEvent (SessionEvent::AutoLoop, SessionEvent::Replace, loc->end(), loc->start(), 0.0f);
- merge_event (event);
+ framepos_t dcp;
+ framecnt_t dcl;
+ auto_loop_declick_range (loc, dcp, dcl);
+ merge_event (new SessionEvent (SessionEvent::AutoLoopDeclick, SessionEvent::Replace, dcp, dcl, 0.0f));
+ merge_event (new SessionEvent (SessionEvent::AutoLoop, SessionEvent::Replace, loc->end(), loc->start(), 0.0f));
- /* locate to start of loop and roll. If doing seamless loop, force a
- locate+buffer refill even if we are positioned there already.
+ /* locate to start of loop and roll.
+
+ args: positition, roll=true, flush=true, with_loop=false, force buffer refill if seamless looping
*/
start_locate (loc->start(), true, true, false, Config->get_seamless_loop());
unset_play_loop ();
}
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC2 with speed = %1\n", _transport_speed));
TransportStateChange ();
}
void
if (target_frame != pos) {
+ if (config.get_jack_time_master()) {
+ /* actually locate now, since otherwise jack_timebase_callback
+ 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);
+ }
+
/* tell JACK to change transport position, and we will
follow along later in ::follow_slave()
*/
/** @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 with_loop, bool force, bool with_mmc)
+Session::locate (framepos_t target_frame, bool with_roll, bool with_flush, bool for_seamless_loop, bool force, bool with_mmc)
{
- if (actively_recording() && !with_loop) {
+ /* 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.
+ */
+
+ if (actively_recording() && !for_seamless_loop) {
return;
}
- if (!force && _transport_frame == target_frame && !loop_changing && !with_loop) {
+ if (!force && _transport_frame == target_frame && !loop_changing && !for_seamless_loop) {
if (with_roll) {
set_transport_speed (1.0, false);
}
return;
}
- if (_transport_speed) {
- /* schedule a declick. we'll be called again when its done */
+ if (_transport_speed && !for_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.
+ */
if (!(transport_sub_state & PendingDeclickOut)) {
transport_sub_state |= (PendingDeclickOut|PendingLocate);
// 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;
*
*/
- if (transport_rolling() && (!auto_play_legal || !config.get_auto_play()) && !with_roll && !(synced_to_jack() && play_loop)) {
+ bool transport_was_stopped = !transport_rolling();
+
+ if (transport_was_stopped && (!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
+ transport_was_stopped = true;
} else {
/* otherwise tell the world that we located */
realtime_locate ();
}
- if (force || !with_loop || loop_changing) {
+ if (force || !for_seamless_loop || loop_changing) {
PostTransportWork todo = PostTransportLocate;
- if (with_roll) {
+ if (with_roll && transport_was_stopped) {
todo = PostTransportWork (todo | PostTransportRoll);
}
/* this is functionally what clear_clicks() does but with a tentative lock */
- Glib::RWLock::WriterLock clickm (click_lock, Glib::TRY_LOCK);
+ Glib::Threads::RWLock::WriterLock clickm (click_lock, Glib::Threads::TRY_LOCK);
if (clickm.locked()) {
/* cancel looped playback if transport pos outside of loop range */
if (play_loop) {
+
Location* al = _locations->auto_loop_location();
- if (al && (_transport_frame < al->start() || _transport_frame > al->end())) {
- // cancel looping directly, this is called from event handling context
- set_play_loop (false);
- }
- else if (al && _transport_frame == al->start()) {
- if (with_loop) {
- // this is only necessary for seamless looping
+ if (al) {
+ if (_transport_frame < al->start() || _transport_frame > al->end()) {
- 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 ()) {
- // tell it we've looped, so it can deal with the record state
- tr->transport_looped(_transport_frame);
+ // located outside the loop: cancel looping directly, this is called from event handling context
+
+ set_play_loop (false);
+
+ } else if (_transport_frame == al->start()) {
+
+ // located to start of loop - this is looping, basically
+
+ if (for_seamless_loop) {
+
+ // this is only necessary for seamless looping
+
+ 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 ()) {
+ // tell it we've looped, so it can deal with the record state
+ tr->transport_looped (_transport_frame);
+ }
}
}
+
+ have_looped = true;
+ TransportLooped(); // EMIT SIGNAL
}
- have_looped = true;
- TransportLooped(); // EMIT SIGNAL
}
}
* @param speed New speed
*/
void
-Session::set_transport_speed (double speed, bool abort, bool clear_state)
+Session::set_transport_speed (double speed, 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\n",
- speed, abort, clear_state, _transport_speed, _transport_frame));
+ 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) {
+ if (as_default && speed == 0.0) { // => reset default transport speed. hacky or what?
+ _default_transport_speed = 1.0;
+ }
return;
}
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",
+ _transport_speed, _transport_frame));
return;
}
_last_transport_speed = _transport_speed;
_transport_speed = speed;
+ if (as_default) {
+ _default_transport_speed = speed;
+ }
+
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);
_butler->schedule_transport_work ();
}
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC3 with speed = %1\n", _transport_speed));
TransportStateChange (); /* EMIT SIGNAL */
}
}
transport_sub_state |= PendingDeclickIn;
- _transport_speed = 1.0;
- _target_transport_speed = 1.0;
+ _transport_speed = _default_transport_speed;
+ _target_transport_speed = _transport_speed;
boost::shared_ptr<RouteList> rl = routes.reader();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
if (tr) {
tr->realtime_set_speed (tr->speed(), true);
}
- (*i)->automation_snapshot (_transport_frame, true);
}
- Timecode::Time time;
- timecode_time_subframes (_transport_frame, time);
- if (!dynamic_cast<MTC_Slave*>(_slave)) {
- MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay));
+ if (!_engine.freewheeling()) {
+ Timecode::Time time;
+ timecode_time_subframes (_transport_frame, time);
+ if (!dynamic_cast<MTC_Slave*>(_slave)) {
+ MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay));
+ }
}
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC4 with speed = %1\n", _transport_speed));
TransportStateChange (); /* EMIT SIGNAL */
}
if (((!config.get_external_sync() && (auto_play_legal && config.get_auto_play())) && !_exporting) || (ptw & PostTransportRoll)) {
start_transport ();
-
} else {
transport_sub_state = 0;
}
delete _slave;
_slave = new_slave;
+ DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", _slave));
+
send_full_time_code (_transport_frame);
boost::shared_ptr<RouteList> rl = routes.reader();
}
break;
+ case LTC:
+#ifdef HAVE_LTC
+ if (_slave && dynamic_cast<LTC_Slave*>(_slave)) {
+ return;
+ }
+
+ try {
+ new_slave = new LTC_Slave (*this);
+ }
+
+ catch (failed_constructor& err) {
+ return;
+ }
+#else
+ return;
+#endif
+ break;
+
case MIDIClock:
if (_slave && dynamic_cast<MIDIClock_Slave*>(_slave)) {
return;
ev = new SessionEvent (SessionEvent::LocateRoll, SessionEvent::Add, SessionEvent::Immediate, range.front().start, 0.0f, false);
merge_event (ev);
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC5 with speed = %1\n", _transport_speed));
TransportStateChange ();
}
the picture.
*/
- g_atomic_int_set (&_butler->should_do_transport_work, 0);
- set_post_transport_work (PostTransportWork (0));
- _butler->stop ();
+ if (_butler) {
+ g_atomic_int_set (&_butler->should_do_transport_work, 0);
+ set_post_transport_work (PostTransportWork (0));
+ _butler->stop ();
+ }
realtime_stop (false, true);
non_realtime_stop (false, 0, ignored);
transport_sub_state = 0;
+ DEBUG_TRACE (DEBUG::Transport, string_compose ("send TSC6 with speed = %1\n", _transport_speed));
TransportStateChange (); /* EMIT SIGNAL */
}
void
Session::send_mmc_locate (framepos_t t)
{
- Timecode::Time time;
- timecode_time_subframes (t, time);
- MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (time));
+ if (!_engine.freewheeling()) {
+ 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