add finite state machine to control/manage transport state
[ardour.git] / libs / ardour / transport_master.cc
index 4f7e69eaeceaa5f7d82297bfeda98ffceeb33058..4d3aa82053d36eeb9c839fb67ebbec4538544dae 100644 (file)
@@ -1,27 +1,30 @@
 /*
-    Copyright (C) 2002 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
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
+ * Copyright (C) 2018-2019 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2018 Robin Gareus <robin@gareus.org>
+ *
* 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
 #include <vector>
 
+#include "pbd/boost_debug.h"
 #include "pbd/debug.h"
+#include "pbd/i18n.h"
 
 #include "ardour/audioengine.h"
+#include "ardour/debug.h"
 #include "ardour/midi_port.h"
 #include "ardour/session.h"
 #include "ardour/transport_master.h"
@@ -29,7 +32,6 @@
 #include "ardour/types_convert.h"
 #include "ardour/utils.h"
 
-#include "pbd/i18n.h"
 
 namespace ARDOUR {
        namespace Properties {
@@ -67,11 +69,12 @@ TransportMaster::TransportMaster (SyncSource t, std::string const & name)
        , _session (0)
        , _current_delta (0)
        , _pending_collect (true)
+       , _removeable (false)
        , _request_mask (Properties::allowed_transport_requests, TransportRequestType (0))
-       , _locked (Properties::locked, false)
        , _sclock_synced (Properties::sclock_synced, false)
        , _collect (Properties::collect, true)
        , _connected (Properties::connected, false)
+       , port_node (X_(""))
 {
        register_properties ();
 
@@ -81,7 +84,59 @@ TransportMaster::TransportMaster (SyncSource t, std::string const & name)
 
 TransportMaster::~TransportMaster()
 {
-       delete _session;
+       DEBUG_TRACE (DEBUG::Destruction, string_compose ("destroying transport master \"%1\" along with port %2\n", name(), (_port ? _port->name() : std::string ("no port"))));
+
+       unregister_port ();
+}
+
+bool
+TransportMaster::speed_and_position (double& speed, samplepos_t& pos, samplepos_t& lp, samplepos_t& when, samplepos_t now)
+{
+       if (!_collect) {
+               return false;
+       }
+
+       if (!locked()) {
+               DEBUG_TRACE (DEBUG::Slave, string_compose ("%1: not locked, no speed and position!\n", name()));
+               return false;
+       }
+
+       SafeTime last;
+       current.safe_read (last);
+
+       if (last.timestamp == 0) {
+               return false;
+       }
+
+       if (last.timestamp && now > last.timestamp && now - last.timestamp > (2.0 * update_interval())) {
+               /* no timecode for two cycles - conclude that it's stopped */
+
+               if (!Config->get_transport_masters_just_roll_when_sync_lost()) {
+                       speed = 0;
+                       pos = last.position;
+                       lp = last.position;
+                       when = last.timestamp;
+                       _current_delta = 0;
+                       DEBUG_TRACE (DEBUG::Slave, string_compose ("%1 not seen since %2 vs %3 (%4) with seekahead = %5 reset pending, pos = %6\n", name(), last.timestamp, now, (now - last.timestamp), update_interval(), pos));
+                       return false;
+               }
+       }
+
+       lp = last.position;
+       when = last.timestamp;
+       speed = last.speed;
+
+       /* provide a .1% deadzone to lock the speed */
+       if (fabs (speed - 1.0) <= 0.001) {
+               speed = 1.0;
+       }
+
+       pos = last.position + (now - last.timestamp) * speed;
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("%1 sync spd: %2 pos: %3 | last-pos: %4 @  %7| elapsed: %5 | speed: %6\n",
+                                                  name(), speed, pos, last.position, (now - last.timestamp), speed, when));
+
+       return true;
 }
 
 void
@@ -90,7 +145,6 @@ TransportMaster::register_properties ()
        _xml_node_name = state_node_name;
 
        add_property (_name);
-       add_property (_locked);
        add_property (_collect);
        add_property (_sclock_synced);
        add_property (_request_mask);
@@ -105,7 +159,7 @@ TransportMaster::set_name (std::string const & str)
 {
        if (_name != str) {
                _name = str;
-               PropertyChange (Properties::name);
+               PropertyChanged (Properties::name);
        }
 }
 
@@ -170,7 +224,16 @@ TransportMaster::check_collect()
 void
 TransportMaster::set_collect (bool yn)
 {
-       _pending_collect = yn;
+       /* theoretical race condition */
+
+       if (_connected) {
+               _pending_collect = yn;
+       } else {
+               if (_collect != yn) {
+                       _pending_collect = _collect = yn;
+                       PropertyChanged (Properties::collect);
+               }
+       }
 }
 
 void
@@ -198,7 +261,27 @@ TransportMaster::set_state (XMLNode const & node, int /* version */)
        XMLNode* pnode = node.child (X_("Port"));
 
        if (pnode) {
-               XMLNodeList const & children = pnode->children();
+               port_node = *pnode;
+
+               if (AudioEngine::instance()->running()) {
+                       connect_port_using_state ();
+               }
+       }
+
+       PropertyChanged (what_changed);
+
+       return 0;
+}
+
+void
+TransportMaster::connect_port_using_state ()
+{
+       if (!_port) {
+               create_port ();
+       }
+
+       if (_port) {
+               XMLNodeList const & children = port_node.children();
                for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
 
                        XMLProperty const *prop;
@@ -211,10 +294,6 @@ TransportMaster::set_state (XMLNode const & node, int /* version */)
                        }
                }
        }
-
-       PropertyChanged (what_changed);
-
-       return 0;
 }
 
 XMLNode&
@@ -222,6 +301,7 @@ TransportMaster::get_state ()
 {
        XMLNode* node = new XMLNode (state_node_name);
        node->set_property (X_("type"), _type);
+       node->set_property (X_("removeable"), _removeable);
 
        add_properties (*node);
 
@@ -266,6 +346,7 @@ TransportMaster::factory (XMLNode const & node)
 
        SyncSource type;
        std::string name;
+       bool removeable;
 
        if (!node.get_property (X_("type"), type)) {
                return boost::shared_ptr<TransportMaster>();
@@ -275,28 +356,110 @@ TransportMaster::factory (XMLNode const & node)
                return boost::shared_ptr<TransportMaster>();
        }
 
-       return factory (type, name);
+       if (!node.get_property (X_("removeable"), removeable)) {
+               /* development versions of 6.0 didn't have this property for a
+                  while. Any TM listed in XML at that time was non-removeable
+               */
+               removeable = false;
+       }
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("xml-construct %1 name %2 removeable %3\n", enum_2_string (type), name, removeable));
+
+       return factory (type, name, removeable);
 }
 
 boost::shared_ptr<TransportMaster>
-TransportMaster::factory (SyncSource type, std::string const& name)
+TransportMaster::factory (SyncSource type, std::string const& name, bool removeable)
 {
        /* XXX need to count existing sources of a given type */
 
-       switch (type) {
+       boost::shared_ptr<TransportMaster> tm;
+
+       DEBUG_TRACE (DEBUG::Slave, string_compose ("factory-construct %1 name %2 removeable %3\n", enum_2_string (type), name, removeable));
+
+       try {
+               switch (type) {
+               case MTC:
+                       tm.reset (new MTC_TransportMaster (name));
+                       break;
+               case LTC:
+                       tm.reset (new LTC_TransportMaster (name));
+                       break;
+               case MIDIClock:
+                       tm.reset (new MIDIClock_TransportMaster (name));
+                       break;
+               case Engine:
+                       tm.reset (new Engine_TransportMaster (*AudioEngine::instance()));
+                       break;
+               default:
+                       break;
+               }
+       } catch (...) {
+               error << string_compose (_("Construction of transport master object of type %1 failed"), enum_2_string (type)) << endmsg;
+               std::cerr << string_compose (_("Construction of transport master object of type %1 failed"), enum_2_string (type)) << std::endl;
+               return boost::shared_ptr<TransportMaster>();
+       }
+
+       if (tm) {
+               tm->set_removeable (removeable);
+       }
+
+       return tm;
+}
+
+/** @param sh Return a short version of the string */
+std::string
+TransportMaster::display_name (bool sh) const
+{
+
+       switch (_type) {
+       case Engine:
+               /* no other backends offer sync for now ... deal with this if we
+                * ever have to.
+                */
+               return S_("SyncSource|JACK");
+
        case MTC:
-               return boost::shared_ptr<TransportMaster> (new MTC_TransportMaster (sync_source_to_string (type)));
-       case LTC:
-               return boost::shared_ptr<TransportMaster> (new LTC_TransportMaster (sync_source_to_string (type)));
+               if (sh) {
+                       if (name().length() <= 4) {
+                               return name();
+                       }
+                       return S_("SyncSource|MTC");
+               } else {
+                       return name();
+               }
+
        case MIDIClock:
-               return boost::shared_ptr<TransportMaster> (new MIDIClock_TransportMaster (sync_source_to_string (type)));
-       case Engine:
-               return boost::shared_ptr<TransportMaster> (new Engine_TransportMaster (*AudioEngine::instance()));
-       default:
-               break;
+               if (sh) {
+                       if (name().length() <= 4) {
+                               return name();
+                       }
+                       return S_("SyncSource|M-Clk");
+               } else {
+                       return name();
+               }
+
+       case LTC:
+               if (sh) {
+                       if (name().length() <= 4) {
+                               return name();
+                       }
+                       return S_("SyncSource|LTC");
+               } else {
+                       return name();
+               }
        }
+       /* GRRRR .... stupid, stupid gcc - you can't get here from there, all enum values are handled */
+       return S_("SyncSource|JACK");
+}
 
-       return boost::shared_ptr<TransportMaster>();
+void
+TransportMaster::unregister_port ()
+{
+       if (_port) {
+               AudioEngine::instance()->unregister_port (_port);
+               _port.reset ();
+       }
 }
 
 boost::shared_ptr<Port>
@@ -304,7 +467,7 @@ TransportMasterViaMIDI::create_midi_port (std::string const & port_name)
 {
        boost::shared_ptr<Port> p;
 
-       if ((p = AudioEngine::instance()->register_input_port (DataType::MIDI, port_name)) == 0) {
+       if ((p = AudioEngine::instance()->register_input_port (DataType::MIDI, port_name, false, TransportMasterPort)) == 0) {
                return boost::shared_ptr<Port> ();
        }