/* 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 { template inline typename BasicLink::SessionState toSessionState( const link::ClientState& state, const bool isConnected) { return {{state.timeline, {state.startStopState.isPlaying, state.startStopState.time}}, 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::OptionalClientStartStopState{{state.startStopState.isPlaying, state.startStopState.time, timestamp}} : link::OptionalClientStartStopState{}; return {timeline, startStopState, timestamp}; } } // namespace detail template inline BasicLink::BasicLink(const double bpm) : 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) { } template inline bool BasicLink::isEnabled() const { return mController.isEnabled(); } template inline void BasicLink::enable(const bool bEnable) { mController.enable(bEnable); } template inline bool BasicLink::isStartStopSyncEnabled() const { return mController.isStartStopSyncEnabled(); } template inline void BasicLink::enableStartStopSync(bool bEnable) { mController.enableStartStopSync(bEnable); } template inline std::size_t BasicLink::numPeers() const { return mController.numPeers(); } template template void BasicLink::setNumPeersCallback(Callback callback) { std::lock_guard lock(mCallbackMutex); mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); }; } template template void BasicLink::setTempoCallback(Callback callback) { std::lock_guard lock(mCallbackMutex); mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); }; } template template void BasicLink::setStartStopCallback(Callback callback) { std::lock_guard lock(mCallbackMutex); mStartStopCallback = callback; } template inline Clock BasicLink::clock() const { return mClock; } template inline typename BasicLink::SessionState BasicLink< Clock>::captureAudioSessionState() const { return detail::toSessionState(mController.clientStateRtSafe(), numPeers() > 0); } template inline void BasicLink::commitAudioSessionState( const typename BasicLink::SessionState state) { mController.setClientStateRtSafe( detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros())); } template inline typename BasicLink::SessionState BasicLink::captureAppSessionState() const { return detail::toSessionState(mController.clientState(), numPeers() > 0); } template inline void BasicLink::commitAppSessionState( const typename BasicLink::SessionState state) { mController.setClientState( detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros())); } // Link::SessionState template inline BasicLink::SessionState::SessionState( const link::ApiState state, const bool bRespectQuantum) : mOriginalState(state) , mState(state) , mbRespectQuantum(bRespectQuantum) { } template inline double BasicLink::SessionState::tempo() const { return mState.timeline.tempo.bpm(); } template inline void BasicLink::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); } template inline double BasicLink::SessionState::beatAtTime( const std::chrono::microseconds time, const double quantum) const { return link::toPhaseEncodedBeats(mState.timeline, time, link::Beats{quantum}) .floating(); } template inline double BasicLink::SessionState::phaseAtTime( const std::chrono::microseconds time, const double quantum) const { return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum}) .floating(); } template inline std::chrono::microseconds BasicLink::SessionState::timeAtBeat( const double beat, const double quantum) const { return link::fromPhaseEncodedBeats( mState.timeline, link::Beats{beat}, link::Beats{quantum}); } template inline void BasicLink::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 forceBeatAtTimeImpl(link::Timeline& timeline, const link::Beats beat, const std::chrono::microseconds time, const link::Beats quantum) { // There are two components to the beat adjustment: a phase shift // and a beat magnitude adjustment. const auto curBeatAtTime = link::toPhaseEncodedBeats(timeline, time, quantum); const auto closestInPhase = link::closestPhaseMatch(curBeatAtTime, beat, quantum); timeline = shiftClientTimeline(timeline, closestInPhase - curBeatAtTime); // Now adjust the magnitude timeline.beatOrigin = timeline.beatOrigin + beat - closestInPhase; } template inline void BasicLink::SessionState::forceBeatAtTime( const double beat, std::chrono::microseconds time, const double quantum) { forceBeatAtTimeImpl(mState.timeline, link::Beats{beat}, time, link::Beats{quantum}); // Due to quantization errors the resulting BeatTime at 'time' after forcing can be // bigger than 'beat' which then violates intended behavior of the API and can lead // i.e. to missing a downbeat. Thus we have to shift the timeline forwards. if (beatAtTime(time, quantum) > beat) { forceBeatAtTimeImpl(mState.timeline, link::Beats{beat}, ++time, link::Beats{quantum}); } } template inline void BasicLink::SessionState::setIsPlaying( const bool isPlaying, const std::chrono::microseconds time) { mState.startStopState = {isPlaying, time}; } template inline bool BasicLink::SessionState::isPlaying() const { return mState.startStopState.isPlaying; } template inline std::chrono::microseconds BasicLink::SessionState::timeForIsPlaying() const { return mState.startStopState.time; } template inline void BasicLink::SessionState::requestBeatAtStartPlayingTime( const double beat, const double quantum) { if (isPlaying()) { requestBeatAtTime(beat, mState.startStopState.time, quantum); } } template inline void BasicLink::SessionState::setIsPlayingAndRequestBeatAtTime( bool isPlaying, std::chrono::microseconds time, double beat, double quantum) { mState.startStopState = {isPlaying, time}; requestBeatAtStartPlayingTime(beat, quantum); } } // namespace ableton