diff --git a/link/include/ableton/discovery/PeerGateway.hpp b/link/include/ableton/discovery/PeerGateway.hpp
--- a/link/include/ableton/discovery/PeerGateway.hpp
+++ b/link/include/ableton/discovery/PeerGateway.hpp
@@ -213,23 +213,21 @@
   return {std::move(messenger), std::move(observer), std::move(io)};
 }
 
-// IpV4 gateway types
 template <typename StateQuery, typename IoContext>
-using IpV4Messenger = UdpMessenger<
+using Messenger = UdpMessenger<
   IpInterface<typename util::Injected<IoContext>::type&, v1::kMaxMessageSize>,
   StateQuery,
   IoContext>;
 
 template <typename PeerObserver, typename StateQuery, typename IoContext>
-using IpV4Gateway =
-  PeerGateway<IpV4Messenger<StateQuery, typename util::Injected<IoContext>::type&>,
+using Gateway =
+  PeerGateway<Messenger<StateQuery, typename util::Injected<IoContext>::type&>,
     PeerObserver,
     IoContext>;
 
 // Factory function to bind a PeerGateway to an IpInterface with the given address.
 template <typename PeerObserver, typename NodeState, typename IoContext>
-IpV4Gateway<PeerObserver, NodeState, IoContext> makeIpV4Gateway(
-  util::Injected<IoContext> io,
+Gateway<PeerObserver, NodeState, IoContext> makeGateway(util::Injected<IoContext> io,
   const IpAddress& addr,
   util::Injected<PeerObserver> observer,
   NodeState state)
diff --git a/link/include/ableton/discovery/Service.hpp b/link/include/ableton/discovery/Service.hpp
--- a/link/include/ableton/discovery/Service.hpp
+++ b/link/include/ableton/discovery/Service.hpp
@@ -33,16 +33,23 @@
   using ServicePeerGateways = PeerGateways<NodeState, GatewayFactory, IoContext>;
 
   Service(NodeState state, GatewayFactory factory, util::Injected<IoContext> io)
-    : mGateways(
+    : mEnabled(false)
+    , mGateways(
         std::chrono::seconds(5), std::move(state), std::move(factory), std::move(io))
   {
   }
 
   void enable(const bool bEnable)
   {
+    mEnabled = bEnable;
     mGateways.enable(bEnable);
   }
 
+  bool isEnabled() const
+  {
+    return mEnabled;
+  }
+
   // Asynchronously operate on the current set of peer gateways. The
   // handler will be invoked in the service's io context.
   template <typename Handler>
@@ -65,6 +72,7 @@
   }
 
 private:
+  bool mEnabled;
   ServicePeerGateways mGateways;
 };
 
diff --git a/link/include/ableton/discovery/UdpMessenger.hpp b/link/include/ableton/discovery/UdpMessenger.hpp
--- a/link/include/ableton/discovery/UdpMessenger.hpp
+++ b/link/include/ableton/discovery/UdpMessenger.hpp
@@ -270,24 +270,42 @@
       // Ignore messages from self and other groups
       if (header.ident != mState.ident() && header.groupId == 0)
       {
-        debug(mIo->log()) << "Received message type "
-                          << static_cast<int>(header.messageType) << " from peer "
-                          << header.ident;
+        // On Linux multicast messages are sent to all sockets registered to the multicast
+        // group. To avoid duplicate message handling and invalid response messages we
+        // check if the message is coming from an endpoint that is in the same subnet as
+        // the interface.
+        auto ignoreIpV4Message = false;
+        if (from.address().is_v4() && mInterface->endpoint().address().is_v4())
+        {
+          const auto subnet = LINK_ASIO_NAMESPACE::ip::make_network_v4(
+            mInterface->endpoint().address().to_v4(), 24);
+          const auto fromAddr =
+            LINK_ASIO_NAMESPACE::ip::make_network_v4(from.address().to_v4(), 32);
+          ignoreIpV4Message = !fromAddr.is_subnet_of(subnet);
+        }
 
-        switch (header.messageType)
+        if (!ignoreIpV4Message)
         {
-        case v1::kAlive:
-          sendResponse(from);
-          receivePeerState(std::move(result.first), result.second, messageEnd);
-          break;
-        case v1::kResponse:
-          receivePeerState(std::move(result.first), result.second, messageEnd);
-          break;
-        case v1::kByeBye:
-          receiveByeBye(std::move(result.first.ident));
-          break;
-        default:
-          info(mIo->log()) << "Unknown message received of type: " << header.messageType;
+          debug(mIo->log()) << "Received message type "
+                            << static_cast<int>(header.messageType) << " from peer "
+                            << header.ident;
+
+          switch (header.messageType)
+          {
+          case v1::kAlive:
+            sendResponse(from);
+            receivePeerState(std::move(result.first), result.second, messageEnd);
+            break;
+          case v1::kResponse:
+            receivePeerState(std::move(result.first), result.second, messageEnd);
+            break;
+          case v1::kByeBye:
+            receiveByeBye(std::move(result.first.ident));
+            break;
+          default:
+            info(mIo->log()) << "Unknown message received of type: "
+                             << header.messageType;
+          }
         }
       }
       listen(tag);
diff --git a/link/include/ableton/discovery/test/Interface.hpp b/link/include/ableton/discovery/test/Interface.hpp
--- a/link/include/ableton/discovery/test/Interface.hpp
+++ b/link/include/ableton/discovery/test/Interface.hpp
@@ -31,6 +31,13 @@
 class Interface
 {
 public:
+  Interface() = default;
+
+  Interface(UdpEndpoint endpoint)
+    : mEndpoint(std::move(endpoint))
+  {
+  }
+
   void send(
     const uint8_t* const bytes, const size_t numBytes, const UdpEndpoint& endpoint)
   {
@@ -56,7 +63,7 @@
 
   UdpEndpoint endpoint() const
   {
-    return UdpEndpoint({}, 0);
+    return mEndpoint;
   }
 
   using SentMessage = std::pair<std::vector<uint8_t>, UdpEndpoint>;
@@ -66,6 +73,7 @@
   using ReceiveCallback =
     std::function<void(const UdpEndpoint&, const std::vector<uint8_t>&)>;
   ReceiveCallback mCallback;
+  UdpEndpoint mEndpoint;
 };
 
 } // namespace test
diff --git a/link/include/ableton/link/Controller.hpp b/link/include/ableton/link/Controller.hpp
--- a/link/include/ableton/link/Controller.hpp
+++ b/link/include/ableton/link/Controller.hpp
@@ -176,7 +176,8 @@
     auto stopped = false;
 
     mIo->async([this, &mutex, &condition, &stopped]() {
-      enable(false);
+      mEnabled = false;
+      mDiscovery.enable(false);
       std::unique_lock<std::mutex> lock(mutex);
       stopped = true;
       condition.notify_one();
@@ -193,18 +194,7 @@
     const bool bWasEnabled = mEnabled.exchange(bEnable);
     if (bWasEnabled != bEnable)
     {
-      mIo->async([this, bEnable] {
-        if (bEnable)
-        {
-          // Process the pending client states to make sure we don't push one after we
-          // have joined a running session
-          mRtClientStateSetter.processPendingClientStates();
-          // Always reset when first enabling to avoid hijacking
-          // tempo in existing sessions
-          resetState();
-        }
-        mDiscovery.enable(bEnable);
-      });
+      mRtClientStateSetter.invoke();
     }
   }
 
@@ -570,11 +560,23 @@
     RtClientStateSetter(Controller& controller)
       : mController(controller)
       , mCallbackDispatcher(
-          [this] { mController.mIo->async([this]() { processPendingClientStates(); }); },
+          [this] {
+            mController.mIo->async([this]() {
+              // Process the pending client states first to make sure we don't push one
+              // after we have joined a running session when enabling
+              processPendingClientStates();
+              updateEnabled();
+            });
+          },
           detail::kRtHandlerFallbackPeriod)
     {
     }
 
+    void invoke()
+    {
+      mCallbackDispatcher.invoke();
+    }
+
     void push(const IncomingClientState clientState)
     {
       if (clientState.timeline)
@@ -598,6 +600,20 @@
     {
       const auto clientState = buildMergedPendingClientState();
       mController.handleRtClientState(clientState);
+    }
+
+    void updateEnabled()
+    {
+      if (mController.mEnabled && !mController.mDiscovery.isEnabled())
+      {
+        // Always reset when first enabling to avoid hijacking tempo in existing sessions
+        mController.resetState();
+        mController.mDiscovery.enable(true);
+      }
+      else if (!mController.mEnabled && mController.mDiscovery.isEnabled())
+      {
+        mController.mDiscovery.enable(false);
+      }
     }
 
   private:
diff --git a/link/include/ableton/link/Gateway.hpp b/link/include/ableton/link/Gateway.hpp
--- a/link/include/ableton/link/Gateway.hpp
+++ b/link/include/ableton/link/Gateway.hpp
@@ -44,7 +44,7 @@
         std::move(ghostXForm),
         std::move(clock),
         util::injectRef(*mIo))
-    , mPeerGateway(discovery::makeIpV4Gateway(util::injectRef(*mIo),
+    , mPeerGateway(discovery::makeGateway(util::injectRef(*mIo),
         std::move(addr),
         std::move(observer),
         PeerState{std::move(nodeState), mMeasurement.endpoint()}))
@@ -84,9 +84,8 @@
 private:
   util::Injected<IoContext> mIo;
   MeasurementService<Clock, typename util::Injected<IoContext>::type&> mMeasurement;
-  discovery::
-    IpV4Gateway<PeerObserver, PeerState, typename util::Injected<IoContext>::type&>
-      mPeerGateway;
+  discovery::Gateway<PeerObserver, PeerState, typename util::Injected<IoContext>::type&>
+    mPeerGateway;
 };
 
 } // namespace link
diff --git a/link/include/ableton/platforms/posix/ScanIpIfAddrs.hpp b/link/include/ableton/platforms/posix/ScanIpIfAddrs.hpp
--- a/link/include/ableton/platforms/posix/ScanIpIfAddrs.hpp
+++ b/link/include/ableton/platforms/posix/ScanIpIfAddrs.hpp
@@ -86,12 +86,13 @@
       const struct ifaddrs* interface;
       for (interface = &interfaces; interface; interface = interface->ifa_next)
       {
-        auto addr = reinterpret_cast<const struct sockaddr_in*>(interface->ifa_addr);
+        const auto addr =
+          reinterpret_cast<const struct sockaddr_in*>(interface->ifa_addr);
         if (addr && interface->ifa_flags & IFF_RUNNING && addr->sin_family == AF_INET)
         {
-          auto bytes = reinterpret_cast<const char*>(&addr->sin_addr);
-          auto address = discovery::makeAddress<discovery::IpAddressV4>(bytes);
-          addrs.emplace_back(std::move(address));
+          const auto bytes = reinterpret_cast<const char*>(&addr->sin_addr);
+          const auto address = discovery::makeAddress<discovery::IpAddressV4>(bytes);
+          addrs.emplace_back(address);
           IpInterfaceNames.insert(std::make_pair(interface->ifa_name, address));
         }
       }
@@ -101,17 +102,19 @@
       const struct ifaddrs* interface;
       for (interface = &interfaces; interface; interface = interface->ifa_next)
       {
-        auto addr = reinterpret_cast<const struct sockaddr_in*>(interface->ifa_addr);
-        if (addr && interface->ifa_flags & IFF_RUNNING && addr->sin_family == AF_INET6)
+        const auto addr =
+          reinterpret_cast<const struct sockaddr_in*>(interface->ifa_addr);
+        if (IpInterfaceNames.find(interface->ifa_name) != IpInterfaceNames.end() && addr
+            && interface->ifa_flags & IFF_RUNNING && addr->sin_family == AF_INET6)
         {
-          auto addr6 = reinterpret_cast<const struct sockaddr_in6*>(addr);
-          auto bytes = reinterpret_cast<const char*>(&addr6->sin6_addr);
-          auto scopeId = addr6->sin6_scope_id;
-          auto address = discovery::makeAddress<discovery::IpAddressV6>(bytes, scopeId);
-          if (IpInterfaceNames.find(interface->ifa_name) != IpInterfaceNames.end()
-              && !address.is_loopback() && address.is_link_local())
+          const auto addr6 = reinterpret_cast<const struct sockaddr_in6*>(addr);
+          const auto bytes = reinterpret_cast<const char*>(&addr6->sin6_addr);
+          const auto scopeId = addr6->sin6_scope_id;
+          const auto address =
+            discovery::makeAddress<discovery::IpAddressV6>(bytes, scopeId);
+          if (!address.is_loopback() && address.is_link_local())
           {
-            addrs.emplace_back(std::move(address));
+            addrs.emplace_back(address);
           }
         }
       }
diff --git a/link/include/ableton/platforms/windows/ScanIpIfAddrs.hpp b/link/include/ableton/platforms/windows/ScanIpIfAddrs.hpp
--- a/link/include/ableton/platforms/windows/ScanIpIfAddrs.hpp
+++ b/link/include/ableton/platforms/windows/ScanIpIfAddrs.hpp
@@ -120,8 +120,9 @@
             // IPv4
             SOCKADDR_IN* addr4 =
               reinterpret_cast<SOCKADDR_IN*>(address->Address.lpSockaddr);
-            auto bytes = reinterpret_cast<const char*>(&addr4->sin_addr);
-            auto ipv4address = discovery::makeAddress<discovery::IpAddressV4>(bytes);
+            const auto bytes = reinterpret_cast<const char*>(&addr4->sin_addr);
+            const auto ipv4address =
+              discovery::makeAddress<discovery::IpAddressV4>(bytes);
             addrs.emplace_back(ipv4address);
             IpInterfaceNames.insert(
               std::make_pair(networkInterface->AdapterName, ipv4address));
@@ -145,8 +146,10 @@
           {
             SOCKADDR_IN6* sockAddr =
               reinterpret_cast<SOCKADDR_IN6*>(address->Address.lpSockaddr);
-            auto bytes = reinterpret_cast<const char*>(&sockAddr->sin6_addr);
-            auto addr6 = discovery::makeAddress<discovery::IpAddressV6>(bytes);
+            const auto scopeId = sockAddr->sin6_scope_id;
+            const auto bytes = reinterpret_cast<const char*>(&sockAddr->sin6_addr);
+            const auto addr6 =
+              discovery::makeAddress<discovery::IpAddressV6>(bytes, scopeId);
             if (!addr6.is_loopback() && addr6.is_link_local())
             {
               addrs.emplace_back(addr6);
diff --git a/link/third_party/catch/catch.hpp b/link/third_party/catch/catch.hpp
--- a/link/third_party/catch/catch.hpp
+++ b/link/third_party/catch/catch.hpp
@@ -1,9 +1,9 @@
 /*
- *  Catch v2.13.7
- *  Generated: 2021-07-28 20:29:27.753164
+ *  Catch v2.13.10
+ *  Generated: 2022-10-16 11:01:23.452308
  *  ----------------------------------------------------------
  *  This file has been merged from multiple headers. Please don't edit it directly
- *  Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved.
+ *  Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved.
  *
  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,7 +15,7 @@
 
 #define CATCH_VERSION_MAJOR 2
 #define CATCH_VERSION_MINOR 13
-#define CATCH_VERSION_PATCH 7
+#define CATCH_VERSION_PATCH 10
 
 #ifdef __clang__
 #    pragma clang system_header
@@ -240,9 +240,6 @@
 // Visual C++
 #if defined(_MSC_VER)
 
-#  define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) )
-#  define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  __pragma( warning(pop) )
-
 // Universal Windows platform does not support SEH
 // Or console colours (or console at all...)
 #  if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
@@ -251,13 +248,18 @@
 #    define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
 #  endif
 
+#  if !defined(__clang__) // Handle Clang masquerading for msvc
+
 // MSVC traditional preprocessor needs some workaround for __VA_ARGS__
 // _MSVC_TRADITIONAL == 0 means new conformant preprocessor
 // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor
-#  if !defined(__clang__) // Handle Clang masquerading for msvc
 #    if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL)
 #      define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
 #    endif // MSVC_TRADITIONAL
+
+// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop`
+#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) )
+#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  __pragma( warning(pop) )
 #  endif // __clang__
 
 #endif // _MSC_VER
@@ -1010,34 +1012,34 @@
 
     #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
         #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \
-            INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ )
+            INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ )
     #else
         #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \
-            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) )
+            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) )
     #endif
 
     #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
         #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \
-            INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ )
+            INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ )
     #else
         #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \
-            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) )
+            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) )
     #endif
 
     #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
         #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \
-            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ )
+            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ )
     #else
         #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \
-            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) )
+            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) )
     #endif
 
     #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
         #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \
-            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ )
+            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ )
     #else
         #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \
-            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )
+            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )
     #endif
 #endif
 
@@ -1050,7 +1052,7 @@
         CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
         static void TestName()
     #define INTERNAL_CATCH_TESTCASE( ... ) \
-        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ )
+        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), __VA_ARGS__ )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
@@ -1072,7 +1074,7 @@
         CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
         void TestName::test()
     #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
-        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ )
+        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), ClassName, __VA_ARGS__ )
 
     ///////////////////////////////////////////////////////////////////////////////
     #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
@@ -1113,18 +1115,18 @@
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
     #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \
-        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ )
+        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ )
 #else
     #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \
-        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) )
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) )
 #endif
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
     #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \
-        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ )
+        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ )
 #else
     #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \
-        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) )
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) )
 #endif
 
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \
@@ -1162,18 +1164,18 @@
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\
-        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename T,__VA_ARGS__)
+        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename T,__VA_ARGS__)
 #else
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\
-        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename T, __VA_ARGS__ ) )
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename T, __VA_ARGS__ ) )
 #endif
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\
-        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__)
+        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__)
 #else
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\
-        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) )
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) )
 #endif
 
     #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList)\
@@ -1204,7 +1206,7 @@
         static void TestFunc()
 
     #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \
-        INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, TmplList )
+        INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, TmplList )
 
     #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \
         CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
@@ -1237,18 +1239,18 @@
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
     #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \
-        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ )
+        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ )
 #else
     #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \
-        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) )
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) )
 #endif
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
     #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \
-        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ )
+        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ )
 #else
     #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \
-        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )
 #endif
 
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\
@@ -1289,18 +1291,18 @@
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\
-        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, typename T, __VA_ARGS__ )
+        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, typename T, __VA_ARGS__ )
 #else
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\
-        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) )
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) )
 #endif
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\
-        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, Signature, __VA_ARGS__ )
+        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature, __VA_ARGS__ )
 #else
     #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\
-        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) )
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) )
 #endif
 
     #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \
@@ -1334,7 +1336,7 @@
         void TestName<TestType>::test()
 
 #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \
-        INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, TmplList )
+        INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, TmplList )
 
 // end catch_test_registry.h
 // start catch_capture.hpp
@@ -3091,7 +3093,7 @@
         Approx operator-() const;
 
         template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
-        Approx operator()( T const& value ) {
+        Approx operator()( T const& value ) const {
             Approx approx( static_cast<double>(value) );
             approx.m_epsilon = m_epsilon;
             approx.m_margin = m_margin;
@@ -4163,7 +4165,7 @@
             if (!m_predicate(m_generator.get())) {
                 // It might happen that there are no values that pass the
                 // filter. In that case we throw an exception.
-                auto has_initial_value = next();
+                auto has_initial_value = nextImpl();
                 if (!has_initial_value) {
                     Catch::throw_exception(GeneratorException("No valid value found in filtered generator"));
                 }
@@ -4175,6 +4177,11 @@
         }
 
         bool next() override {
+            return nextImpl();
+        }
+
+    private:
+        bool nextImpl() {
             bool success = m_generator.next();
             if (!success) {
                 return false;
@@ -7388,8 +7395,6 @@
             template <typename T, bool Destruct>
             struct ObjectStorage
             {
-                using TStorage = typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type;
-
                 ObjectStorage() : data() {}
 
                 ObjectStorage(const ObjectStorage& other)
@@ -7432,7 +7437,7 @@
                     return *static_cast<T*>(static_cast<void*>(&data));
                 }
 
-                TStorage data;
+                struct { alignas(T) unsigned char data[sizeof(T)]; }  data;
             };
         }
 
@@ -7942,7 +7947,7 @@
     #if defined(__i386__) || defined(__x86_64__)
         #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */
     #elif defined(__aarch64__)
-        #define CATCH_TRAP()  __asm__(".inst 0xd4200000")
+        #define CATCH_TRAP()  __asm__(".inst 0xd43e0000")
     #endif
 
 #elif defined(CATCH_PLATFORM_IPHONE)
@@ -13385,6 +13390,10 @@
                     filename.erase(0, lastSlash);
                     filename[0] = '#';
                 }
+                else
+                {
+                    filename.insert(0, "#");
+                }
 
                 auto lastDot = filename.find_last_of('.');
                 if (lastDot != std::string::npos) {
@@ -13547,7 +13556,7 @@
 
             // Handle list request
             if( Option<std::size_t> listed = list( m_config ) )
-                return static_cast<int>( *listed );
+                return (std::min) (MaxExitCode, static_cast<int>(*listed));
 
             TestGroup tests { m_config };
             auto const totals = tests.execute();
@@ -15380,7 +15389,7 @@
     }
 
     Version const& libraryVersion() {
-        static Version version( 2, 13, 7, "", 0 );
+        static Version version( 2, 13, 10, "", 0 );
         return version;
     }
 
@@ -17515,12 +17524,20 @@
 
 #ifndef __OBJC__
 
+#ifndef CATCH_INTERNAL_CDECL
+#ifdef _MSC_VER
+#define CATCH_INTERNAL_CDECL __cdecl
+#else
+#define CATCH_INTERNAL_CDECL
+#endif
+#endif
+
 #if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
 // Standard C/C++ Win32 Unicode wmain entry point
-extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) {
+extern "C" int CATCH_INTERNAL_CDECL wmain (int argc, wchar_t * argv[], wchar_t * []) {
 #else
 // Standard C/C++ main entry point
-int main (int argc, char * argv[]) {
+int CATCH_INTERNAL_CDECL main (int argc, char * argv[]) {
 #endif
 
     return Catch::Session().run( argc, argv );
@@ -17648,9 +17665,9 @@
 
 #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)
 #define CATCH_BENCHMARK(...) \
-    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))
+    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))
 #define CATCH_BENCHMARK_ADVANCED(name) \
-    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name)
+    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), name)
 #endif // CATCH_CONFIG_ENABLE_BENCHMARKING
 
 // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
@@ -17752,9 +17769,9 @@
 
 #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)
 #define BENCHMARK(...) \
-    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))
+    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))
 #define BENCHMARK_ADVANCED(name) \
-    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name)
+    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), name)
 #endif // CATCH_CONFIG_ENABLE_BENCHMARKING
 
 using Catch::Detail::Approx;
@@ -17801,8 +17818,8 @@
 #define CATCH_WARN( msg )          (void)(0)
 #define CATCH_CAPTURE( msg )       (void)(0)
 
-#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
-#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))
+#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))
 #define CATCH_METHOD_AS_TEST_CASE( method, ... )
 #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0)
 #define CATCH_SECTION( ... )
@@ -17811,7 +17828,7 @@
 #define CATCH_FAIL_CHECK( ... ) (void)(0)
 #define CATCH_SUCCEED( ... ) (void)(0)
 
-#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
 #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)
@@ -17834,8 +17851,8 @@
 #endif
 
 // "BDD-style" convenience wrappers
-#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
-#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className )
+#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))
+#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), className )
 #define CATCH_GIVEN( desc )
 #define CATCH_AND_GIVEN( desc )
 #define CATCH_WHEN( desc )
@@ -17883,10 +17900,10 @@
 #define INFO( msg ) (void)(0)
 #define UNSCOPED_INFO( msg ) (void)(0)
 #define WARN( msg ) (void)(0)
-#define CAPTURE( msg ) (void)(0)
+#define CAPTURE( ... ) (void)(0)
 
-#define TEST_CASE( ... )  INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
-#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define TEST_CASE( ... )  INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))
+#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))
 #define METHOD_AS_TEST_CASE( method, ... )
 #define REGISTER_TEST_CASE( Function, ... ) (void)(0)
 #define SECTION( ... )
@@ -17894,7 +17911,7 @@
 #define FAIL( ... ) (void)(0)
 #define FAIL_CHECK( ... ) (void)(0)
 #define SUCCEED( ... ) (void)(0)
-#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))
 
 #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
 #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)
@@ -17924,8 +17941,8 @@
 #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )
 
 // "BDD-style" convenience wrappers
-#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) )
-#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className )
+#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ) )
+#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), className )
 
 #define GIVEN( desc )
 #define AND_GIVEN( desc )
diff --git a/src/hs/Sound/Tidal/Clock.hs b/src/hs/Sound/Tidal/Clock.hs
new file mode 100644
--- /dev/null
+++ b/src/hs/Sound/Tidal/Clock.hs
@@ -0,0 +1,350 @@
+module Sound.Tidal.Clock where
+
+import Control.Concurrent (forkIO, threadDelay)
+import Control.Concurrent.STM (TVar, atomically, modifyTVar', newTVar, readTVar, retry, swapTVar)
+import Control.Monad (when)
+import Control.Monad.Reader (ReaderT, ask, runReaderT)
+import Control.Monad.State (StateT, evalStateT, get, liftIO, modify, put)
+import Data.Coerce (coerce)
+import Data.Int (Int64)
+import Foreign.C.Types (CDouble (..))
+import qualified Sound.Osc.Fd as O
+import qualified Sound.Tidal.Link as Link
+import System.IO (hPutStrLn, stderr)
+
+type Time = Rational
+
+-- | representation of a tick based clock
+type Clock =
+  ReaderT ClockMemory (StateT ClockState IO)
+
+-- | internal read-only memory of the clock
+data ClockMemory = ClockMemory
+  { clockConfig :: ClockConfig,
+    clockRef :: ClockRef,
+    clockAction :: TickAction
+  }
+
+-- | internal mutable state of the clock
+data ClockState = ClockState
+  { ticks :: Int64,
+    start :: Link.Micros,
+    nowArc :: (Time, Time),
+    nudged :: Double
+  }
+  deriving (Show)
+
+-- | reference to interact with the clock, while it is running
+data ClockRef = ClockRef
+  { rAction :: TVar ClockAction,
+    rAbletonLink :: Link.AbletonLink
+  }
+
+-- | configuration of the clock
+data ClockConfig = ClockConfig
+  { cQuantum :: CDouble,
+    cBeatsPerCycle :: CDouble,
+    cFrameTimespan :: Double,
+    cEnableLink :: Bool,
+    cSkipTicks :: Int64,
+    cProcessAhead :: Double
+  }
+
+-- | action to be executed on a tick,
+-- | given the current timespan, nudge and reference to the clock
+type TickAction =
+  (Time, Time) -> Double -> ClockConfig -> ClockRef -> (Link.SessionState, Link.SessionState) -> IO ()
+
+-- | possible actions for interacting with the clock
+data ClockAction
+  = NoAction
+  | SetCycle Time
+  | SetTempo Time
+  | SetNudge Double
+
+defaultCps :: Double
+defaultCps = 0.575
+
+defaultConfig :: ClockConfig
+defaultConfig =
+  ClockConfig
+    { cFrameTimespan = 1 / 20,
+      cEnableLink = False,
+      cProcessAhead = 3 / 10,
+      cSkipTicks = 10,
+      cQuantum = 4,
+      cBeatsPerCycle = 4
+    }
+
+-- | creates a clock according to the config and runs it
+-- | in a seperate thread
+clocked :: ClockConfig -> TickAction -> IO ClockRef
+clocked config ac = runClock config ac clockCheck
+
+-- | runs the clock on the initial state and memory as given
+-- | by initClock, hands the ClockRef for interaction from outside
+runClock :: ClockConfig -> TickAction -> Clock () -> IO ClockRef
+runClock config ac clock = do
+  (mem, st) <- initClock config ac
+  _ <- forkIO $ evalStateT (runReaderT clock mem) st
+  return (clockRef mem)
+
+-- | creates a ableton link instance and an MVar for interacting
+-- | with the clock from outside and computes the initial clock state
+initClock :: ClockConfig -> TickAction -> IO (ClockMemory, ClockState)
+initClock config ac = do
+  abletonLink <- Link.create bpm
+  when (cEnableLink config) $ Link.enable abletonLink
+  sessionState <- Link.createAndCaptureAppSessionState abletonLink
+  now <- Link.clock abletonLink
+  let startAt = now + processAhead
+  Link.requestBeatAtTime sessionState 0 startAt (cQuantum config)
+  Link.commitAndDestroyAppSessionState abletonLink sessionState
+  clockMV <- atomically $ newTVar NoAction
+  let st =
+        ClockState
+          { ticks = 0,
+            start = now,
+            nowArc = (0, 0),
+            nudged = 0
+          }
+  return (ClockMemory config (ClockRef clockMV abletonLink) ac, st)
+  where
+    processAhead = round $ (cProcessAhead config) * 1000000
+    bpm = (coerce defaultCps) * 60 * (cBeatsPerCycle config)
+
+-- The reference time Link uses,
+-- is the time the audio for a certain beat hits the speaker.
+-- Processing of the nowArc should happen early enough for
+-- all events in the nowArc to hit the speaker, but not too early.
+-- Processing thus needs to happen a short while before the start
+-- of nowArc. How far ahead is controlled by cProcessAhead.
+
+-- previously called checkArc
+clockCheck :: Clock ()
+clockCheck = do
+  (ClockMemory config (ClockRef clockMV abletonLink) _) <- ask
+
+  action <- liftIO $ atomically $ swapTVar clockMV NoAction
+  processAction action
+
+  st <- get
+
+  let logicalEnd = logicalTime config (start st) $ ticks st + 1
+      nextArcStartCycle = arcEnd $ nowArc st
+
+  ss <- liftIO $ Link.createAndCaptureAppSessionState abletonLink
+  arcStartTime <- liftIO $ cyclesToTime config ss nextArcStartCycle
+  liftIO $ Link.destroySessionState ss
+
+  if (arcStartTime < logicalEnd)
+    then clockProcess
+    else tick
+
+-- tick moves the logical time forward or recalculates the ticks in case
+-- the logical time is out of sync with Link time.
+-- tick delays the thread when logical time is ahead of Link time.
+tick :: Clock ()
+tick = do
+  (ClockMemory config (ClockRef _ abletonLink) _) <- ask
+  st <- get
+  now <- liftIO $ Link.clock abletonLink
+  let processAhead = round $ (cProcessAhead config) * 1000000
+      frameTimespan = round $ (cFrameTimespan config) * 1000000
+      preferredNewTick = ticks st + 1
+      logicalNow = logicalTime config (start st) preferredNewTick
+      aheadOfNow = now + processAhead
+      actualTick = (aheadOfNow - start st) `div` frameTimespan
+      drifted = abs (actualTick - preferredNewTick) > (cSkipTicks config)
+      newTick
+        | drifted = actualTick
+        | otherwise = preferredNewTick
+      delta = min frameTimespan (logicalNow - aheadOfNow)
+
+  put $ st {ticks = newTick}
+
+  if drifted
+    then liftIO $ hPutStrLn stderr $ "skip: " ++ (show (actualTick - ticks st))
+    else when (delta > 0) $ liftIO $ threadDelay $ fromIntegral delta
+
+  clockCheck
+
+-- previously called processArc
+-- hands the current link operations to the TickAction
+clockProcess :: Clock ()
+clockProcess = do
+  (ClockMemory config ref@(ClockRef _ abletonLink) action) <- ask
+  st <- get
+  let logicalEnd = logicalTime config (start st) $ ticks st + 1
+      startCycle = arcEnd $ nowArc st
+
+  sessionState <- liftIO $ Link.createAndCaptureAppSessionState abletonLink
+  endCycle <- liftIO $ timeToCycles config sessionState logicalEnd
+
+  liftIO $ action (startCycle, endCycle) (nudged st) config ref (sessionState, sessionState)
+
+  liftIO $ Link.commitAndDestroyAppSessionState abletonLink sessionState
+
+  put (st {nowArc = (startCycle, endCycle)})
+  tick
+
+processAction :: ClockAction -> Clock ()
+processAction NoAction = return ()
+processAction (SetNudge n) = modify (\st -> st {nudged = n})
+processAction (SetTempo bpm) = do
+  (ClockMemory _ (ClockRef _ abletonLink) _) <- ask
+  sessionState <- liftIO $ Link.createAndCaptureAppSessionState abletonLink
+  now <- liftIO $ Link.clock abletonLink
+  liftIO $ Link.setTempo sessionState (fromRational bpm) now
+  liftIO $ Link.commitAndDestroyAppSessionState abletonLink sessionState
+processAction (SetCycle cyc) = do
+  (ClockMemory config (ClockRef _ abletonLink) _) <- ask
+  sessionState <- liftIO $ Link.createAndCaptureAppSessionState abletonLink
+
+  now <- liftIO $ Link.clock abletonLink
+  let processAhead = round $ (cProcessAhead config) * 1000000
+      startAt = now + processAhead
+      beat = (fromRational cyc) * (cBeatsPerCycle config)
+  liftIO $ Link.requestBeatAtTime sessionState beat startAt (cQuantum config)
+  liftIO $ Link.commitAndDestroyAppSessionState abletonLink sessionState
+
+  modify (\st -> st {ticks = 0, start = now, nowArc = (cyc, cyc)})
+
+---------------------------------------------------------------
+----------- functions representing link operations ------------
+---------------------------------------------------------------
+
+arcStart :: (Time, Time) -> Time
+arcStart = fst
+
+arcEnd :: (Time, Time) -> Time
+arcEnd = snd
+
+beatToCycles :: ClockConfig -> Double -> Double
+beatToCycles config beat = beat / (coerce $ cBeatsPerCycle config)
+
+cyclesToBeat :: ClockConfig -> Double -> Double
+cyclesToBeat config cyc = cyc * (coerce $ cBeatsPerCycle config)
+
+getSessionState :: ClockRef -> IO Link.SessionState
+getSessionState (ClockRef _ abletonLink) = Link.createAndCaptureAppSessionState abletonLink
+
+-- onSingleTick assumes it runs at beat 0.
+-- The best way to achieve that is to use forceBeatAtTime.
+-- But using forceBeatAtTime means we can not commit its session state.
+getZeroedSessionState :: ClockConfig -> ClockRef -> IO Link.SessionState
+getZeroedSessionState config (ClockRef _ abletonLink) = do
+  ss <- Link.createAndCaptureAppSessionState abletonLink
+  nowLink <- liftIO $ Link.clock abletonLink
+  Link.forceBeatAtTime ss 0 (nowLink + processAhead) (cQuantum config)
+  return ss
+  where
+    processAhead = round $ (cProcessAhead config) * 1000000
+
+getTempo :: Link.SessionState -> IO Time
+getTempo ss = fmap toRational $ Link.getTempo ss
+
+setTempoCPS :: Time -> Link.Micros -> ClockConfig -> Link.SessionState -> IO ()
+setTempoCPS cps now conf ss = Link.setTempo ss (coerce $ cyclesToBeat conf ((fromRational cps) * 60)) now
+
+timeAtBeat :: ClockConfig -> Link.SessionState -> Double -> IO Link.Micros
+timeAtBeat config ss beat = Link.timeAtBeat ss (coerce beat) (cQuantum config)
+
+timeToCycles :: ClockConfig -> Link.SessionState -> Link.Micros -> IO Time
+timeToCycles config ss time = do
+  beat <- Link.beatAtTime ss time (cQuantum config)
+  return $! (toRational beat) / (toRational (cBeatsPerCycle config))
+
+-- At what time does the cycle occur according to Link?
+cyclesToTime :: ClockConfig -> Link.SessionState -> Time -> IO Link.Micros
+cyclesToTime config ss cyc = do
+  let beat = (fromRational cyc) * (cBeatsPerCycle config)
+  Link.timeAtBeat ss beat (cQuantum config)
+
+linkToOscTime :: ClockRef -> Link.Micros -> IO O.Time
+linkToOscTime (ClockRef _ abletonLink) lt = do
+  nowOsc <- O.time
+  nowLink <- liftIO $ Link.clock abletonLink
+  return $ addMicrosToOsc (lt - nowLink) nowOsc
+
+addMicrosToOsc :: Link.Micros -> O.Time -> O.Time
+addMicrosToOsc m t = ((fromIntegral m) / 1000000) + t
+
+-- Time is processed at a fixed rate according to configuration
+-- logicalTime gives the time when a tick starts based on when
+-- processing first started.
+logicalTime :: ClockConfig -> Link.Micros -> Int64 -> Link.Micros
+logicalTime config startTime ticks' = startTime + ticks' * frameTimespan
+  where
+    frameTimespan = round $ (cFrameTimespan config) * 1000000
+
+---------------------------------------------------------------
+----------- functions for interacting with the clock ----------
+---------------------------------------------------------------
+
+getBPM :: ClockRef -> IO Time
+getBPM (ClockRef _ abletonLink) = do
+  ss <- Link.createAndCaptureAppSessionState abletonLink
+  bpm <- Link.getTempo ss
+  Link.destroySessionState ss
+  return $! toRational bpm
+
+getCPS :: ClockConfig -> ClockRef -> IO Time
+getCPS config ref = fmap (\bpm -> bpm / (toRational $ cBeatsPerCycle config) / 60) (getBPM ref)
+
+getCycleTime :: ClockConfig -> ClockRef -> IO Time
+getCycleTime config (ClockRef _ abletonLink) = do
+  now <- Link.clock abletonLink
+  ss <- Link.createAndCaptureAppSessionState abletonLink
+  c <- timeToCycles config ss now
+  Link.destroySessionState ss
+  return $! c
+
+resetClock :: ClockRef -> IO ()
+resetClock clock = setClock clock 0
+
+setClock :: ClockRef -> Time -> IO ()
+setClock (ClockRef clock _) t = atomically $ do
+  action <- readTVar clock
+  case action of
+    NoAction -> modifyTVar' clock (const $ SetCycle t)
+    _ -> retry
+
+setBPM :: ClockRef -> Time -> IO ()
+setBPM (ClockRef clock _) t = atomically $ do
+  action <- readTVar clock
+  case action of
+    NoAction -> modifyTVar' clock (const $ SetTempo t)
+    _ -> retry
+
+setCPS :: ClockConfig -> ClockRef -> Time -> IO ()
+setCPS config ref cps = setBPM ref bpm
+  where
+    bpm = cps * 60 * (toRational $ cBeatsPerCycle config)
+
+setNudge :: ClockRef -> Double -> IO ()
+setNudge (ClockRef clock _) n = atomically $ do
+  action <- readTVar clock
+  case action of
+    NoAction -> modifyTVar' clock (const $ SetNudge n)
+    _ -> retry
+
+-- Used for Tempo callback
+-- Tempo changes will be applied.
+-- However, since the full arc is processed at once and since Link does not support
+-- scheduling, tempo change may affect scheduling of events that happen earlier
+-- in the normal stream (the one handled by onTick).
+clockOnce :: TickAction -> ClockConfig -> ClockRef -> IO ()
+clockOnce action config ref@(ClockRef _ abletonLink) = do
+  ss <- getZeroedSessionState config ref
+  temposs <- Link.createAndCaptureAppSessionState abletonLink
+  -- The nowArc is a full cycle
+  action (0, 1) 0 config ref (ss, temposs)
+  Link.destroySessionState ss
+  Link.commitAndDestroyAppSessionState abletonLink temposs
+
+disableLink :: ClockRef -> IO ()
+disableLink (ClockRef _ abletonLink) = Link.disable abletonLink
+
+enableLink :: ClockRef -> IO ()
+enableLink (ClockRef _ abletonLink) = Link.enable abletonLink
diff --git a/tidal-link.cabal b/tidal-link.cabal
--- a/tidal-link.cabal
+++ b/tidal-link.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.4
 name:                tidal-link
-version:             1.0.3
+version:             1.1.0
 synopsis:            Ableton Link integration for Tidal
 -- description:
 homepage:            http://tidalcycles.org/
@@ -12,7 +12,7 @@
 Copyright:           (c) Pierre Krafft and contributors, 2021
 category:            Sound
 build-type:          Simple
-tested-with:         GHC == 9.2.4
+tested-with:         GHC == 9.8.2
 
 extra-source-files:
   README.md
@@ -29,9 +29,13 @@
   default-language:    Haskell2010
 
   exposed-modules:     Sound.Tidal.Link
+                       Sound.Tidal.Clock
 
   build-depends:
-      base >=4.8 && <5
+      base >=4.8 && < 5,
+      hosc >= 0.21 && < 0.22,
+      mtl >= 2.2 && < 2.4,
+      stm >= 2.5 && < 2.6
 
   if os(windows)
     extra-libraries:
@@ -46,7 +50,7 @@
   else
     cxx-options:
       -DLINK_PLATFORM_LINUX=1 -std=c++14 -Wno-multichar -Wno-subobject-linkage
-  
+
   if impl(ghc >= 9.4)
     build-depends: system-cxx-std-lib
   else
@@ -65,7 +69,7 @@
   type:     git
   location: https://github.com/tidalcycles/Tidal
 
-executable linktest
+executable tidal-linktest
   ghc-options: -Wall
   hs-source-dirs:
                  src/hs
