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