Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
5.4KB

  1. /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
  2. *
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation, either version 2 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. * If you would like to incorporate Link into a proprietary software application,
  17. * please contact <link-devs@ableton.com>.
  18. */
  19. #pragma once
  20. #include <ableton/discovery/Socket.hpp>
  21. #include <ableton/link/GhostXForm.hpp>
  22. #include <ableton/link/Kalman.hpp>
  23. #include <ableton/link/LinearRegression.hpp>
  24. #include <ableton/link/Measurement.hpp>
  25. #include <ableton/link/PeerState.hpp>
  26. #include <ableton/link/PingResponder.hpp>
  27. #include <ableton/link/SessionId.hpp>
  28. #include <ableton/link/v1/Messages.hpp>
  29. #include <ableton/platforms/asio/AsioService.hpp>
  30. #include <ableton/util/Log.hpp>
  31. #include <map>
  32. #include <memory>
  33. #include <thread>
  34. namespace ableton
  35. {
  36. namespace link
  37. {
  38. template <typename Clock, typename Log>
  39. class MeasurementService
  40. {
  41. public:
  42. using Point = std::pair<double, double>;
  43. using MeasurementInstance = Measurement<platforms::asio::AsioService,
  44. Clock,
  45. discovery::Socket<v1::kMaxMessageSize>,
  46. Log>;
  47. using MeasurementServicePingResponder = PingResponder<platforms::asio::AsioService&,
  48. Clock,
  49. discovery::Socket<v1::kMaxMessageSize>,
  50. Log>;
  51. static const std::size_t kNumberThreads = 1;
  52. MeasurementService(asio::ip::address_v4 address,
  53. SessionId sessionId,
  54. GhostXForm ghostXForm,
  55. Clock clock,
  56. util::Injected<Log> log)
  57. : mClock(std::move(clock))
  58. , mLog(std::move(log))
  59. , mPingResponder(std::move(address),
  60. std::move(sessionId),
  61. std::move(ghostXForm),
  62. util::injectRef(mIo),
  63. mClock,
  64. mLog)
  65. {
  66. }
  67. MeasurementService(const MeasurementService&) = delete;
  68. MeasurementService(MeasurementService&&) = delete;
  69. ~MeasurementService()
  70. {
  71. // Clear the measurement map in the io service so that whatever
  72. // cleanup code executes in response to the destruction of the
  73. // measurement objects still have access to the io service
  74. mIo.post([this] { mMeasurementMap.clear(); });
  75. }
  76. void updateNodeState(const SessionId& sessionId, const GhostXForm& xform)
  77. {
  78. mPingResponder.updateNodeState(sessionId, xform);
  79. }
  80. asio::ip::udp::endpoint endpoint() const
  81. {
  82. return mPingResponder.endpoint();
  83. }
  84. // Measure the peer and invoke the handler with a GhostXForm
  85. template <typename Handler>
  86. void measurePeer(const PeerState& state, const Handler handler)
  87. {
  88. using namespace std;
  89. mIo.post([this, state, handler] {
  90. const auto nodeId = state.nodeState.nodeId;
  91. auto addr = mPingResponder.endpoint().address().to_v4();
  92. auto callback = CompletionCallback<Handler>{*this, nodeId, handler};
  93. try
  94. {
  95. mMeasurementMap[nodeId] =
  96. MeasurementInstance{state, move(callback), move(addr), mClock, mLog};
  97. }
  98. catch (const runtime_error& err)
  99. {
  100. info(*mLog) << "Failed to measure. Reason: " << err.what();
  101. handler(GhostXForm{});
  102. }
  103. });
  104. }
  105. static GhostXForm filter(
  106. std::vector<Point>::const_iterator begin, std::vector<Point>::const_iterator end)
  107. {
  108. using namespace std;
  109. using std::chrono::microseconds;
  110. Kalman<5> kalman;
  111. for (auto it = begin; it != end; ++it)
  112. {
  113. kalman.iterate(it->second - it->first);
  114. }
  115. return GhostXForm{1, microseconds(llround(kalman.getValue()))};
  116. }
  117. private:
  118. template <typename Handler>
  119. struct CompletionCallback
  120. {
  121. void operator()(const std::vector<Point> data)
  122. {
  123. using namespace std;
  124. using std::chrono::microseconds;
  125. // Post this to the measurement service's io service so that we
  126. // don't delete the measurement object in its stack. Capture all
  127. // needed data separately from this, since this object may be
  128. // gone by the time the block gets executed.
  129. auto nodeId = mNodeId;
  130. auto handler = mHandler;
  131. auto& measurementMap = mService.mMeasurementMap;
  132. mService.mIo.post([nodeId, handler, &measurementMap, data] {
  133. const auto it = measurementMap.find(nodeId);
  134. if (it != measurementMap.end())
  135. {
  136. if (data.empty())
  137. {
  138. handler(GhostXForm{});
  139. }
  140. else
  141. {
  142. handler(MeasurementService::filter(begin(data), end(data)));
  143. }
  144. measurementMap.erase(it);
  145. }
  146. });
  147. }
  148. MeasurementService& mService;
  149. NodeId mNodeId;
  150. Handler mHandler;
  151. };
  152. // Make sure the measurement map outlives the io service so that the rest of
  153. // the members are guaranteed to be valid when any final handlers
  154. // are begin run.
  155. using MeasurementMap = std::map<NodeId, MeasurementInstance>;
  156. MeasurementMap mMeasurementMap;
  157. Clock mClock;
  158. util::Injected<Log> mLog;
  159. platforms::asio::AsioService mIo;
  160. MeasurementServicePingResponder mPingResponder;
  161. };
  162. } // namespace link
  163. } // namespace ableton