#pragma once
#include <ableton/link/Phase.hpp>
namespace ableton
{
namespace detail
{
template <typename Clock>
inline typename BasicLink<Clock>::SessionState toSessionState(
const link::ClientState& state, const bool isConnected)
{
return {{state.timeline,
state.timelineSessionId,
{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};
}
}
template <typename Clock>
inline BasicLink<Clock>::BasicLink(const double bpm)
: mController(
link::Tempo(bpm),
[this](const std::size_t peers)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mPeerCountCallback(peers);
},
[this](const link::Tempo tempo)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mTempoCallback(tempo);
},
[this](const bool isPlaying)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mStartStopCallback(isPlaying);
},
mClock)
{
}
template <typename Clock>
inline bool BasicLink<Clock>::isEnabled() const
{
return mController.isEnabled();
}
template <typename Clock>
inline void BasicLink<Clock>::enable(const bool bEnable)
{
mController.enable(bEnable);
}
template <typename Clock>
inline bool BasicLink<Clock>::isStartStopSyncEnabled() const
{
return mController.isStartStopSyncEnabled();
}
template <typename Clock>
inline void BasicLink<Clock>::enableStartStopSync(bool bEnable)
{
mController.enableStartStopSync(bEnable);
}
template <typename Clock>
inline std::size_t BasicLink<Clock>::numPeers() const
{
return mController.numPeers();
}
template <typename Clock>
template <typename Callback>
void BasicLink<Clock>::setNumPeersCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); };
}
template <typename Clock>
template <typename Callback>
void BasicLink<Clock>::setTempoCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); };
}
template <typename Clock>
template <typename Callback>
void BasicLink<Clock>::setStartStopCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mStartStopCallback = callback;
}
template <typename Clock>
inline Clock BasicLink<Clock>::clock() const
{
return mClock;
}
template <typename Clock>
inline typename BasicLink<Clock>::SessionState BasicLink<
Clock>::captureAudioSessionState() const
{
return detail::toSessionState<Clock>(mController.clientStateRtSafe(), numPeers() > 0);
}
template <typename Clock>
inline void BasicLink<Clock>::commitAudioSessionState(
const typename BasicLink<Clock>::SessionState state)
{
mController.setClientStateRtSafe(
detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros()));
}
template <typename Clock>
inline typename BasicLink<Clock>::SessionState BasicLink<Clock>::captureAppSessionState()
const
{
return detail::toSessionState<Clock>(mController.clientState(), numPeers() > 0);
}
template <typename Clock>
inline void BasicLink<Clock>::commitAppSessionState(
const typename BasicLink<Clock>::SessionState state)
{
mController.setClientState(
detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros()));
}
template <typename Clock>
inline BasicLink<Clock>::SessionState::SessionState(const link::ApiState state,
const bool bRespectQuantum)
: mOriginalState(state)
, mState(state)
, mbRespectQuantum(bRespectQuantum)
{
}
template <typename Clock>
inline bool BasicLink<Clock>::SessionState::operator==(const SessionState& other) const
{
return std::tie(mOriginalState, mState, mbRespectQuantum)
== std::tie(other.mOriginalState, other.mState, other.mbRespectQuantum);
}
template <typename Clock>
inline bool BasicLink<Clock>::SessionState::operator!=(const SessionState& other) const
{
return !(operator==(other));
}
template <typename Clock>
inline double BasicLink<Clock>::SessionState::tempo() const
{
return mState.timeline.tempo.bpm();
}
template <typename Clock>
inline void BasicLink<Clock>::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 <typename Clock>
inline double BasicLink<Clock>::SessionState::beatAtTime(
const std::chrono::microseconds time, const double quantum) const
{
return link::toPhaseEncodedBeats(mState.timeline, time, link::Beats{quantum})
.floating();
}
template <typename Clock>
inline double BasicLink<Clock>::SessionState::phaseAtTime(
const std::chrono::microseconds time, const double quantum) const
{
return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum})
.floating();
}
template <typename Clock>
inline std::chrono::microseconds BasicLink<Clock>::SessionState::timeAtBeat(
const double beat, const double quantum) const
{
return link::fromPhaseEncodedBeats(
mState.timeline, link::Beats{beat}, link::Beats{quantum});
}
template <typename Clock>
inline void BasicLink<Clock>::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)
{
const auto curBeatAtTime = link::toPhaseEncodedBeats(timeline, time, quantum);
const auto closestInPhase = link::closestPhaseMatch(curBeatAtTime, beat, quantum);
timeline = shiftClientTimeline(timeline, closestInPhase - curBeatAtTime);
timeline.beatOrigin = timeline.beatOrigin + beat - closestInPhase;
}
template <typename Clock>
inline void BasicLink<Clock>::SessionState::forceBeatAtTime(
const double beat, std::chrono::microseconds time, const double quantum)
{
forceBeatAtTimeImpl(mState.timeline, link::Beats{beat}, time, link::Beats{quantum});
if (beatAtTime(time, quantum) > beat)
{
forceBeatAtTimeImpl(mState.timeline, link::Beats{beat}, ++time, link::Beats{quantum});
}
}
template <typename Clock>
inline void BasicLink<Clock>::SessionState::setIsPlaying(
const bool isPlaying, const std::chrono::microseconds time)
{
mState.startStopState = {isPlaying, time};
}
template <typename Clock>
inline bool BasicLink<Clock>::SessionState::isPlaying() const
{
return mState.startStopState.isPlaying;
}
template <typename Clock>
inline std::chrono::microseconds BasicLink<Clock>::SessionState::timeForIsPlaying() const
{
return mState.startStopState.time;
}
template <typename Clock>
inline void BasicLink<Clock>::SessionState::requestBeatAtStartPlayingTime(
const double beat, const double quantum)
{
if (isPlaying())
{
requestBeatAtTime(beat, mState.startStopState.time, quantum);
}
}
template <typename Clock>
inline void BasicLink<Clock>::SessionState::setIsPlayingAndRequestBeatAtTime(
bool isPlaying, std::chrono::microseconds time, double beat, double quantum)
{
mState.startStopState = {isPlaying, time};
requestBeatAtStartPlayingTime(beat, quantum);
}
}