X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fmtc_slave.cc;h=4ca86236b737c113f8587bdffabfad3f30b61d43;hb=9e9cb3bf31a8cbf00ecf43ea0c3acd8b8bb86760;hp=e45e76025112e4987fb005db1ea752fe397d873d;hpb=799b6ec97d2dea160ae11326fda8f50ae6c34faf;p=ardour.git diff --git a/libs/ardour/mtc_slave.cc b/libs/ardour/mtc_slave.cc index e45e760251..4ca86236b7 100644 --- a/libs/ardour/mtc_slave.cc +++ b/libs/ardour/mtc_slave.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2002-4 Paul Davis + Copyright (C) 2002-4 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,29 +21,32 @@ #include #include #include -#include -#include -#include +#include "pbd/error.h" +#include "pbd/failed_constructor.h" +#include "pbd/pthread_utils.h" -#include -#include -#include -#include -#include +#include "midi++/port.h" +#include "ardour/debug.h" +#include "ardour/slave.h" +#include "ardour/session.h" +#include "ardour/audioengine.h" +#include "ardour/cycles.h" #include "i18n.h" +using namespace std; using namespace ARDOUR; using namespace sigc; using namespace MIDI; using namespace PBD; -MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p) +MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p) : session (s) { can_notify_on_unknown_rate = true; - - last_mtc_fps_byte = session.get_mtc_smpte_bits (); + did_reset_tc_format = false; + + last_mtc_fps_byte = session.get_mtc_timecode_bits (); rebind (p); reset (); @@ -51,6 +54,9 @@ MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p) MTC_Slave::~MTC_Slave() { + if (did_reset_tc_format) { + session.config.set_timecode_format (saved_tc_format); + } } void @@ -68,159 +74,239 @@ MTC_Slave::rebind (MIDI::Port& p) } void -MTC_Slave::update_mtc_qtr (Parser& p) +MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, nframes_t now) { - cycles_t cnow = get_cycles (); - nframes_t now = session.engine().frame_time(); - nframes_t qtr; - static cycles_t last_qtr = 0; - - qtr = (long) (session.frames_per_smpte_frame() / 4); - mtc_frame += qtr; - last_qtr = cnow; - - current.guard1++; - current.position = mtc_frame; - current.timestamp = now; - current.guard2++; + DEBUG_TRACE (DEBUG::MTC, string_compose ("qtr frame %1 at %2, valid-for-time? %3\n", which_qtr, now, qtr_frame_messages_valid_for_time)); + + if (qtr_frame_messages_valid_for_time) { + + if (which_qtr != 7) { + + /* leave position and speed updates for the last + qtr frame message of the 8 to be taken + care of in update_mtc_time(), invoked + by the Parser right after this. + */ + + nframes_t qtr; + + qtr = (long) (session.frames_per_timecode_frame() / 4); + mtc_frame += qtr; + + double speed = compute_apparent_speed (now); + + current.guard1++; + current.position = mtc_frame; + current.timestamp = now; + current.speed = speed; + current.guard2++; + } - last_inbound_frame = now; + last_inbound_frame = now; + } } void -MTC_Slave::update_mtc_time (const byte *msg, bool was_full) +MTC_Slave::update_mtc_time (const byte *msg, bool was_full, nframes_t now) { - nframes_t now = session.engine().frame_time(); - SMPTE::Time smpte; + /* "now" can be zero if this is called from a context where we do not have or do not want + to use a timestamp indicating when this MTC time was received. + */ + + Timecode::Time timecode; + TimecodeFormat tc_format; + bool reset_tc = true; + + DEBUG_TRACE (DEBUG::MTC, string_compose ("full mtc time known at %1, full ? %2\n", now, was_full)); - smpte.hours = msg[3]; - smpte.minutes = msg[2]; - smpte.seconds = msg[1]; - smpte.frames = msg[0]; + timecode.hours = msg[3]; + timecode.minutes = msg[2]; + timecode.seconds = msg[1]; + timecode.frames = msg[0]; last_mtc_fps_byte = msg[4]; - + switch (msg[4]) { case MTC_24_FPS: - smpte.rate = 24; - smpte.drop = false; + timecode.rate = 24; + timecode.drop = false; + tc_format = timecode_24; can_notify_on_unknown_rate = true; break; case MTC_25_FPS: - smpte.rate = 25; - smpte.drop = false; + timecode.rate = 25; + timecode.drop = false; + tc_format = timecode_25; can_notify_on_unknown_rate = true; break; case MTC_30_FPS_DROP: - smpte.rate = 30; - smpte.drop = true; + timecode.rate = 30; + timecode.drop = true; + tc_format = timecode_30drop; can_notify_on_unknown_rate = true; break; case MTC_30_FPS: - smpte.rate = 30; - smpte.drop = false; + timecode.rate = 30; + timecode.drop = false; can_notify_on_unknown_rate = true; + tc_format = timecode_30; break; default: /* throttle error messages about unknown MTC rates */ if (can_notify_on_unknown_rate) { error << string_compose (_("Unknown rate/drop value %1 in incoming MTC stream, session values used instead"), - (int) msg[4]) + (int) msg[4]) << endmsg; can_notify_on_unknown_rate = false; } - smpte.rate = session.smpte_frames_per_second(); - smpte.drop = session.smpte_drop_frames(); + timecode.rate = session.timecode_frames_per_second(); + timecode.drop = session.timecode_drop_frames(); + reset_tc = false; } - session.smpte_to_sample (smpte, mtc_frame, true, false); - + if (reset_tc) { + if (!did_reset_tc_format) { + saved_tc_format = session.config.get_timecode_format(); + did_reset_tc_format = true; + } + session.config.set_timecode_format (tc_format); + } + + DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC time timestamp = %1 TC %2 = frame %3 (from full message ? %4)\n", + now, timecode, mtc_frame, was_full)); + if (was_full) { - - current.guard1++; - current.position = mtc_frame; - current.timestamp = 0; - current.guard2++; - - session.request_locate (mtc_frame, false); + + session.timecode_to_sample (timecode, mtc_frame, true, false); + session.request_locate (mtc_frame, false); session.request_transport_speed (0); - update_mtc_status (MIDI::Parser::MTC_Stopped); + update_mtc_status (MIDI::Parser::MTC_Stopped); + + reset (); - reset (); - } else { + + + /* we've had the first set of 8 qtr frame messages, determine position + and allow continuing qtr frame messages to provide position + and speed information. + */ + + qtr_frame_messages_valid_for_time = true; + session.timecode_to_sample (timecode, mtc_frame, true, false); /* We received the last quarter frame 7 quarter frames (1.75 mtc frames) after the instance when the contents of the mtc quarter frames were decided. Add time to compensate for the elapsed 1.75 - frames. - Also compensate for audio latency. + frames. Also compensate for audio latency. */ + + mtc_frame += (long) (1.75 * session.frames_per_timecode_frame()) + session.worst_output_latency(); + + if (now) { + double speed = compute_apparent_speed (now); + + current.guard1++; + current.position = mtc_frame; + current.timestamp = now; + current.speed = speed; + current.guard2++; + } + } + + if (now) { + last_inbound_frame = now; + } +} + +double +MTC_Slave::compute_apparent_speed (nframes64_t now) +{ + if (current.timestamp != 0) { + + double speed = (double) ((mtc_frame - current.position) / (double) (now - current.timestamp)); + DEBUG_TRACE (DEBUG::MTC, string_compose ("instantaneous speed = %1 from %2 / %3\n", + speed, mtc_frame - current.position, now - current.timestamp)); + + /* crude low pass filter/smoother for speed */ - mtc_frame += (long) (1.75 * session.frames_per_smpte_frame()) + session.worst_output_latency(); + accumulator[accumulator_index++] = speed; + + if (accumulator_index >= accumulator_size) { + have_first_accumulated_speed = true; + accumulator_index = 0; + } - if (first_mtc_frame == 0) { - first_mtc_frame = mtc_frame; - first_mtc_time = now; + if (have_first_accumulated_speed) { + double total = 0; + + for (int32_t i = 0; i < accumulator_size; ++i) { + total += accumulator[i]; + } + + speed = total / accumulator_size; + DEBUG_TRACE (DEBUG::MTC, string_compose ("speed smoothed to %1\n", speed)); } + + return speed; - current.guard1++; - current.position = mtc_frame; - current.timestamp = now; - current.guard2++; + } else { + + return 0; } - - last_inbound_frame = now; } void MTC_Slave::handle_locate (const MIDI::byte* mmc_tc) { MIDI::byte mtc[5]; - + mtc[4] = last_mtc_fps_byte; mtc[3] = mmc_tc[0] & 0xf; /* hrs only */ mtc[2] = mmc_tc[1]; mtc[1] = mmc_tc[2]; mtc[0] = mmc_tc[3]; - update_mtc_time (mtc, true); + update_mtc_time (mtc, true, 0); } void MTC_Slave::update_mtc_status (MIDI::Parser::MTC_Status status) { + /* XXX !!! thread safety ... called from MIDI I/O context + and process() context (via ::speed_and_position()) + */ switch (status) { case MTC_Stopped: - mtc_speed = 0.0f; mtc_frame = 0; current.guard1++; current.position = mtc_frame; current.timestamp = 0; + current.speed = 0; current.guard2++; break; case MTC_Forward: - mtc_speed = 0.0f; mtc_frame = 0; current.guard1++; current.position = mtc_frame; current.timestamp = 0; + current.speed = 0; current.guard2++; break; case MTC_Backward: - mtc_speed = 0.0f; mtc_frame = 0; current.guard1++; current.position = mtc_frame; current.timestamp = 0; + current.speed = 0; current.guard2++; break; @@ -231,13 +317,13 @@ void MTC_Slave::read_current (SafeTime *st) const { int tries = 0; + do { if (tries == 10) { error << _("MTC Slave: atomic read of current time failed, sleeping!") << endmsg; usleep (20); tries = 0; } - *st = current; tries++; @@ -256,71 +342,49 @@ MTC_Slave::ok() const return true; } -bool -MTC_Slave::speed_and_position (double& speed, nframes_t& pos) +bool +MTC_Slave::speed_and_position (double& speed, nframes64_t& pos) { - nframes_t now = session.engine().frame_time(); + nframes64_t now = session.engine().frame_time(); SafeTime last; - nframes_t frame_rate; nframes_t elapsed; - double speed_now; read_current (&last); - if (first_mtc_time == 0) { + if (last.timestamp == 0) { speed = 0; pos = last.position; + DEBUG_TRACE (DEBUG::MTC, string_compose ("first call to MTC_Slave::speed_and_position, pos = %1\n", last.position)); return true; } - + /* no timecode for 1/4 second ? conclude that its stopped */ if (last_inbound_frame && now > last_inbound_frame && now - last_inbound_frame > session.frame_rate() / 4) { - mtc_speed = 0; + speed = 0; pos = last.position; session.request_locate (pos, false); session.request_transport_speed (0); update_mtc_status (MIDI::Parser::MTC_Stopped); reset(); + DEBUG_TRACE (DEBUG::MTC, "MTC not seen for 1/4 second - reset\n"); return false; } - frame_rate = session.frame_rate(); - - speed_now = (double) ((last.position - first_mtc_frame) / (double) (now - first_mtc_time)); - - accumulator[accumulator_index++] = speed_now; - - if (accumulator_index >= accumulator_size) { - have_first_accumulated_speed = true; - accumulator_index = 0; - } + DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::speed_and_position %1 %2\n", last.speed, last.position)); - if (have_first_accumulated_speed) { - double total = 0; - - for (int32_t i = 0; i < accumulator_size; ++i) { - total += accumulator[i]; - } - - mtc_speed = total / accumulator_size; - - } else { - - mtc_speed = speed_now; - - } - - if (mtc_speed == 0.0f) { + if (last.speed == 0.0f) { elapsed = 0; } else { - + /* scale elapsed time by the current MTC speed */ - + if (last.timestamp && (now > last.timestamp)) { - elapsed = (nframes_t) floor (mtc_speed * (now - last.timestamp)); + elapsed = (nframes_t) floor (last.speed * (now - last.timestamp)); + DEBUG_TRACE (DEBUG::MTC, string_compose ("last timecode received @ %1, now = %2, elapsed frames = %3 w/speed= %4\n", + last.timestamp, now, elapsed, speed)); } else { elapsed = 0; /* XXX is this right? */ } @@ -329,15 +393,17 @@ MTC_Slave::speed_and_position (double& speed, nframes_t& pos) /* now add the most recent timecode value plus the estimated elapsed interval */ pos = elapsed + last.position; + speed = last.speed; + + DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::speed_and_position FINAL %1 %2\n", last.speed, pos)); - speed = mtc_speed; return true; } ARDOUR::nframes_t MTC_Slave::resolution() const { - return (nframes_t) session.frames_per_smpte_frame(); + return (nframes_t) session.frames_per_timecode_frame(); } void @@ -347,18 +413,16 @@ MTC_Slave::reset () be being updated as we call this. but this supposed to be a realtime-safe call. */ - + port->input()->reset_mtc_state (); - + last_inbound_frame = 0; current.guard1++; current.position = 0; current.timestamp = 0; + current.speed = 0; current.guard2++; - first_mtc_frame = 0; - first_mtc_time = 0; - accumulator_index = 0; have_first_accumulated_speed = false; - mtc_speed = 0; + qtr_frame_messages_valid_for_time = false; }