/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace detail { inline Link::SessionState toSessionState( const link::ClientState& state, const bool isConnected) { const auto time = state.timeline.fromBeats(state.startStopState.beats); const auto startStopState = link::ApiStartStopState{state.startStopState.isPlaying, time}; return {{state.timeline, startStopState}, isConnected}; } inline link::IncomingClientState toIncomingClientState(const link::ApiState& state, const link::ApiState& originalState, const std::chrono::microseconds timestamp) { const auto timeline = originalState.timeline != state.timeline ? link::OptionalTimeline{state.timeline} : link::OptionalTimeline{}; const auto startStopState = originalState.startStopState != state.startStopState ? link::OptionalStartStopState{{state.startStopState.isPlaying, state.timeline.toBeats(state.startStopState.time), timestamp}} : link::OptionalStartStopState{}; return {timeline, startStopState, timestamp}; } } // namespace detail inline Link::Link(const double bpm) : mPeerCountCallback([](std::size_t) {}) , mTempoCallback([](link::Tempo) {}) , mStartStopCallback([](bool) {}) , mClock{} , mController(link::Tempo(bpm), [this](const std::size_t peers) { std::lock_guard lock(mCallbackMutex); mPeerCountCallback(peers); }, [this](const link::Tempo tempo) { std::lock_guard lock(mCallbackMutex); mTempoCallback(tempo); }, [this](const bool isPlaying) { std::lock_guard lock(mCallbackMutex); mStartStopCallback(isPlaying); }, mClock, util::injectVal(link::platform::IoContext{})) { } inline bool Link::isEnabled() const { return mController.isEnabled(); } inline void Link::enable(const bool bEnable) { mController.enable(bEnable); } inline bool Link::isStartStopSyncEnabled() const { return mController.isStartStopSyncEnabled(); } inline void Link::enableStartStopSync(bool bEnable) { mController.enableStartStopSync(bEnable); } inline std::size_t Link::numPeers() const { return mController.numPeers(); } template void Link::setNumPeersCallback(Callback callback) { std::lock_guard lock(mCallbackMutex); mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); }; } template void Link::setTempoCallback(Callback callback) { std::lock_guard lock(mCallbackMutex); mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); }; } template void Link::setStartStopCallback(Callback callback) { std::lock_guard lock(mCallbackMutex); mStartStopCallback = callback; } inline Link::Clock Link::clock() const { return mClock; } inline Link::SessionState Link::captureAudioSessionState() const { return detail::toSessionState(mController.clientStateRtSafe(), numPeers() > 0); } inline void Link::commitAudioSessionState(const Link::SessionState state) { mController.setClientStateRtSafe( detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros())); } inline Link::SessionState Link::captureAppSessionState() const { return detail::toSessionState(mController.clientState(), numPeers() > 0); } inline void Link::commitAppSessionState(const Link::SessionState state) { mController.setClientState( detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros())); } // Link::SessionState inline Link::SessionState::SessionState( const link::ApiState state, const bool bRespectQuantum) : mOriginalState(state) , mState(state) , mbRespectQuantum(bRespectQuantum) { } inline double Link::SessionState::tempo() const { return mState.timeline.tempo.bpm(); } inline void Link::SessionState::setTempo( const double bpm, const std::chrono::microseconds atTime) { const auto desiredTl = link::clampTempo( link::Timeline{link::Tempo(bpm), mState.timeline.toBeats(atTime), atTime}); mState.timeline.tempo = desiredTl.tempo; mState.timeline.timeOrigin = desiredTl.fromBeats(mState.timeline.beatOrigin); } inline double Link::SessionState::beatAtTime( const std::chrono::microseconds time, const double quantum) const { return link::toPhaseEncodedBeats(mState.timeline, time, link::Beats{quantum}) .floating(); } inline double Link::SessionState::phaseAtTime( const std::chrono::microseconds time, const double quantum) const { return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum}) .floating(); } inline std::chrono::microseconds Link::SessionState::timeAtBeat( const double beat, const double quantum) const { return link::fromPhaseEncodedBeats( mState.timeline, link::Beats{beat}, link::Beats{quantum}); } inline void Link::SessionState::requestBeatAtTime( const double beat, std::chrono::microseconds time, const double quantum) { if (mbRespectQuantum) { time = timeAtBeat(link::nextPhaseMatch(link::Beats{beatAtTime(time, quantum)}, link::Beats{beat}, link::Beats{quantum}) .floating(), quantum); } forceBeatAtTime(beat, time, quantum); } inline void Link::SessionState::forceBeatAtTime( const double beat, const std::chrono::microseconds time, const double quantum) { // There are two components to the beat adjustment: a phase shift // and a beat magnitude adjustment. const auto curBeatAtTime = link::Beats{beatAtTime(time, quantum)}; const auto closestInPhase = link::closestPhaseMatch(curBeatAtTime, link::Beats{beat}, link::Beats{quantum}); mState.timeline = shiftClientTimeline(mState.timeline, closestInPhase - curBeatAtTime); // Now adjust the magnitude mState.timeline.beatOrigin = mState.timeline.beatOrigin + (link::Beats{beat} - closestInPhase); } inline void Link::SessionState::setIsPlaying( const bool isPlaying, const std::chrono::microseconds time) { mState.startStopState = {isPlaying, time}; } inline bool Link::SessionState::isPlaying() const { return mState.startStopState.isPlaying; } inline std::chrono::microseconds Link::SessionState::timeForIsPlaying() const { return mState.startStopState.time; } inline void Link::SessionState::requestBeatAtStartPlayingTime( const double beat, const double quantum) { if (isPlaying()) { requestBeatAtTime(beat, mState.startStopState.time, quantum); } } inline void Link::SessionState::setIsPlayingAndRequestBeatAtTime( bool isPlaying, std::chrono::microseconds time, double beat, double quantum) { mState.startStopState = {isPlaying, time}; requestBeatAtStartPlayingTime(beat, quantum); } } // namespace ableton