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 
20 #pragma once
21 
22 #include <ableton/link/GhostXForm.hpp>
23 #include <ableton/link/Kalman.hpp>
24 #include <ableton/link/LinearRegression.hpp>
25 #include <ableton/link/Measurement.hpp>
26 #include <ableton/link/PeerState.hpp>
27 #include <ableton/link/PingResponder.hpp>
28 #include <ableton/link/SessionId.hpp>
29 #include <ableton/link/v1/Messages.hpp>
30 #include <map>
31 #include <memory>
32 
33 namespace ableton
34 {
35 namespace link
36 {
37 
38 template <typename Clock, typename IoContext>
39 class MeasurementService
40 {
41 public:
42   using IoType = util::Injected<IoContext>;
43   using Point = std::pair<double, double>;
44   using MeasurementInstance = Measurement<Clock, IoContext>;
45 
MeasurementService(asio::ip::address_v4 address,SessionId sessionId,GhostXForm ghostXForm,Clock clock,IoType io)46   MeasurementService(asio::ip::address_v4 address,
47     SessionId sessionId,
48     GhostXForm ghostXForm,
49     Clock clock,
50     IoType io)
51     : mClock(std::move(clock))
52     , mIo(std::move(io))
53     , mPingResponder(std::move(address),
54         std::move(sessionId),
55         std::move(ghostXForm),
56         mClock,
57         util::injectRef(*mIo))
58   {
59   }
60 
61   MeasurementService(const MeasurementService&) = delete;
62   MeasurementService(MeasurementService&&) = delete;
63 
~MeasurementService()64   ~MeasurementService()
65   {
66     // Clear the measurement map in the IoContext so that whatever
67     // cleanup code executes in response to the destruction of the
68     // measurement objects still have access to the IoContext.
69     mIo->async([this] { mMeasurementMap.clear(); });
70   }
71 
updateNodeState(const SessionId & sessionId,const GhostXForm & xform)72   void updateNodeState(const SessionId& sessionId, const GhostXForm& xform)
73   {
74     mPingResponder.updateNodeState(sessionId, xform);
75   }
76 
endpoint() const77   asio::ip::udp::endpoint endpoint() const
78   {
79     return mPingResponder.endpoint();
80   }
81 
82   // Measure the peer and invoke the handler with a GhostXForm
83   template <typename Handler>
measurePeer(const PeerState & state,const Handler handler)84   void measurePeer(const PeerState& state, const Handler handler)
85   {
86     using namespace std;
87 
88     mIo->async([this, state, handler] {
89       const auto nodeId = state.nodeState.nodeId;
90       auto addr = mPingResponder.endpoint().address().to_v4();
91       auto callback = CompletionCallback<Handler>{*this, nodeId, handler};
92 
93       try
94       {
95 
96         mMeasurementMap[nodeId] =
97           std::unique_ptr<MeasurementInstance>(new MeasurementInstance{
98             state, move(callback), move(addr), mClock, mIo->clone()});
99       }
100       catch (const runtime_error& err)
101       {
102         info(mIo->log()) << "gateway@" + addr.to_string()
103                          << " Failed to measure. Reason: " << err.what();
104         handler(GhostXForm{});
105       }
106     });
107   }
108 
filter(std::vector<Point>::const_iterator begin,std::vector<Point>::const_iterator end)109   static GhostXForm filter(
110     std::vector<Point>::const_iterator begin, std::vector<Point>::const_iterator end)
111   {
112     using namespace std;
113     using std::chrono::microseconds;
114 
115     Kalman<5> kalman;
116     for (auto it = begin; it != end; ++it)
117     {
118       kalman.iterate(it->second - it->first);
119     }
120 
121     return GhostXForm{1, microseconds(llround(kalman.getValue()))};
122   }
123 
124 private:
125   template <typename Handler>
126   struct CompletionCallback
127   {
operator ()ableton::link::MeasurementService::CompletionCallback128     void operator()(const std::vector<Point> data)
129     {
130       using namespace std;
131       using std::chrono::microseconds;
132 
133       // Post this to the measurement service's IoContext so that we
134       // don't delete the measurement object in its stack. Capture all
135       // needed data separately from this, since this object may be
136       // gone by the time the block gets executed.
137       auto nodeId = mNodeId;
138       auto handler = mHandler;
139       auto& measurementMap = mMeasurementService.mMeasurementMap;
140       mMeasurementService.mIo->async([nodeId, handler, &measurementMap, data] {
141         const auto it = measurementMap.find(nodeId);
142         if (it != measurementMap.end())
143         {
144           if (data.empty())
145           {
146             handler(GhostXForm{});
147           }
148           else
149           {
150             handler(MeasurementService::filter(begin(data), end(data)));
151           }
152           measurementMap.erase(it);
153         }
154       });
155     }
156 
157     MeasurementService& mMeasurementService;
158     NodeId mNodeId;
159     Handler mHandler;
160   };
161 
162   // Make sure the measurement map outlives the IoContext so that the rest of
163   // the members are guaranteed to be valid when any final handlers
164   // are begin run.
165   using MeasurementMap = std::map<NodeId, std::unique_ptr<MeasurementInstance>>;
166   MeasurementMap mMeasurementMap;
167   Clock mClock;
168   IoType mIo;
169   PingResponder<Clock, IoContext> mPingResponder;
170 };
171 
172 } // namespace link
173 } // namespace ableton
174