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/Phase.hpp>
23
24namespace ableton
25{
26namespace detail
27{
28
29inline Link::SessionState toSessionState(
30  const link::ClientState& state, const bool isConnected)
31{
32  const auto time = state.timeline.fromBeats(state.startStopState.beats);
33  const auto startStopState =
34    link::ApiStartStopState{state.startStopState.isPlaying, time};
35  return {{state.timeline, startStopState}, isConnected};
36}
37
38inline link::IncomingClientState toIncomingClientState(const link::ApiState& state,
39  const link::ApiState& originalState,
40  const std::chrono::microseconds timestamp)
41{
42  const auto timeline = originalState.timeline != state.timeline
43                          ? link::OptionalTimeline{state.timeline}
44                          : link::OptionalTimeline{};
45  const auto startStopState =
46    originalState.startStopState != state.startStopState
47      ? link::OptionalStartStopState{{state.startStopState.isPlaying,
48          state.timeline.toBeats(state.startStopState.time), timestamp}}
49      : link::OptionalStartStopState{};
50  return {timeline, startStopState, timestamp};
51}
52
53} // namespace detail
54
55inline Link::Link(const double bpm)
56  : mPeerCountCallback([](std::size_t) {})
57  , mTempoCallback([](link::Tempo) {})
58  , mStartStopCallback([](bool) {})
59  , mClock{}
60  , mController(link::Tempo(bpm),
61      [this](const std::size_t peers) {
62        std::lock_guard<std::mutex> lock(mCallbackMutex);
63        mPeerCountCallback(peers);
64      },
65      [this](const link::Tempo tempo) {
66        std::lock_guard<std::mutex> lock(mCallbackMutex);
67        mTempoCallback(tempo);
68      },
69      [this](const bool isPlaying) {
70        std::lock_guard<std::mutex> lock(mCallbackMutex);
71        mStartStopCallback(isPlaying);
72      },
73      mClock,
74      util::injectVal(link::platform::IoContext{}))
75{
76}
77
78inline bool Link::isEnabled() const
79{
80  return mController.isEnabled();
81}
82
83inline void Link::enable(const bool bEnable)
84{
85  mController.enable(bEnable);
86}
87
88inline bool Link::isStartStopSyncEnabled() const
89{
90  return mController.isStartStopSyncEnabled();
91}
92
93inline void Link::enableStartStopSync(bool bEnable)
94{
95  mController.enableStartStopSync(bEnable);
96}
97
98inline std::size_t Link::numPeers() const
99{
100  return mController.numPeers();
101}
102
103template <typename Callback>
104void Link::setNumPeersCallback(Callback callback)
105{
106  std::lock_guard<std::mutex> lock(mCallbackMutex);
107  mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); };
108}
109
110template <typename Callback>
111void Link::setTempoCallback(Callback callback)
112{
113  std::lock_guard<std::mutex> lock(mCallbackMutex);
114  mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); };
115}
116
117template <typename Callback>
118void Link::setStartStopCallback(Callback callback)
119{
120  std::lock_guard<std::mutex> lock(mCallbackMutex);
121  mStartStopCallback = callback;
122}
123
124inline Link::Clock Link::clock() const
125{
126  return mClock;
127}
128
129inline Link::SessionState Link::captureAudioSessionState() const
130{
131  return detail::toSessionState(mController.clientStateRtSafe(), numPeers() > 0);
132}
133
134inline void Link::commitAudioSessionState(const Link::SessionState state)
135{
136  mController.setClientStateRtSafe(
137    detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros()));
138}
139
140inline Link::SessionState Link::captureAppSessionState() const
141{
142  return detail::toSessionState(mController.clientState(), numPeers() > 0);
143}
144
145inline void Link::commitAppSessionState(const Link::SessionState state)
146{
147  mController.setClientState(
148    detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros()));
149}
150
151// Link::SessionState
152
153inline Link::SessionState::SessionState(
154  const link::ApiState state, const bool bRespectQuantum)
155  : mOriginalState(state)
156  , mState(state)
157  , mbRespectQuantum(bRespectQuantum)
158{
159}
160
161inline double Link::SessionState::tempo() const
162{
163  return mState.timeline.tempo.bpm();
164}
165
166inline void Link::SessionState::setTempo(
167  const double bpm, const std::chrono::microseconds atTime)
168{
169  const auto desiredTl = link::clampTempo(
170    link::Timeline{link::Tempo(bpm), mState.timeline.toBeats(atTime), atTime});
171  mState.timeline.tempo = desiredTl.tempo;
172  mState.timeline.timeOrigin = desiredTl.fromBeats(mState.timeline.beatOrigin);
173}
174
175inline double Link::SessionState::beatAtTime(
176  const std::chrono::microseconds time, const double quantum) const
177{
178  return link::toPhaseEncodedBeats(mState.timeline, time, link::Beats{quantum})
179    .floating();
180}
181
182inline double Link::SessionState::phaseAtTime(
183  const std::chrono::microseconds time, const double quantum) const
184{
185  return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum})
186    .floating();
187}
188
189inline std::chrono::microseconds Link::SessionState::timeAtBeat(
190  const double beat, const double quantum) const
191{
192  return link::fromPhaseEncodedBeats(
193    mState.timeline, link::Beats{beat}, link::Beats{quantum});
194}
195
196inline void Link::SessionState::requestBeatAtTime(
197  const double beat, std::chrono::microseconds time, const double quantum)
198{
199  if (mbRespectQuantum)
200  {
201    time = timeAtBeat(link::nextPhaseMatch(link::Beats{beatAtTime(time, quantum)},
202                        link::Beats{beat}, link::Beats{quantum})
203                        .floating(),
204      quantum);
205  }
206  forceBeatAtTime(beat, time, quantum);
207}
208
209inline void Link::SessionState::forceBeatAtTime(
210  const double beat, const std::chrono::microseconds time, const double quantum)
211{
212  // There are two components to the beat adjustment: a phase shift
213  // and a beat magnitude adjustment.
214  const auto curBeatAtTime = link::Beats{beatAtTime(time, quantum)};
215  const auto closestInPhase =
216    link::closestPhaseMatch(curBeatAtTime, link::Beats{beat}, link::Beats{quantum});
217  mState.timeline = shiftClientTimeline(mState.timeline, closestInPhase - curBeatAtTime);
218  // Now adjust the magnitude
219  mState.timeline.beatOrigin =
220    mState.timeline.beatOrigin + (link::Beats{beat} - closestInPhase);
221}
222
223inline void Link::SessionState::setIsPlaying(
224  const bool isPlaying, const std::chrono::microseconds time)
225{
226  mState.startStopState = {isPlaying, time};
227}
228
229inline bool Link::SessionState::isPlaying() const
230{
231  return mState.startStopState.isPlaying;
232}
233
234inline std::chrono::microseconds Link::SessionState::timeForIsPlaying() const
235{
236  return mState.startStopState.time;
237}
238
239inline void Link::SessionState::requestBeatAtStartPlayingTime(
240  const double beat, const double quantum)
241{
242  if (isPlaying())
243  {
244    requestBeatAtTime(beat, mState.startStopState.time, quantum);
245  }
246}
247
248inline void Link::SessionState::setIsPlayingAndRequestBeatAtTime(
249  bool isPlaying, std::chrono::microseconds time, double beat, double quantum)
250{
251  mState.startStopState = {isPlaying, time};
252  requestBeatAtStartPlayingTime(beat, quantum);
253}
254
255} // namespace ableton
256