11namespace tracktion {
inline namespace engine
20 bool isConnected()
const
25 void activateTimer (
bool isActive)
34 setSpeedCompensation (0.0);
40 if (! isConnected() || ! transport.
isPlaying())
45 const auto localBarsAndBeatsPos = seq.toBarsAndBeats (pos);
46 const auto numerator =
static_cast<double> (localBarsAndBeatsPos.numerator);
49 const double bps = seq.getBeatsPerSecondAt (pos).v;
51 const auto outputLatencyBeats = ((dm.getBlockLength() * 2).inSeconds() + dm.getOutputLatencySeconds()) * bps;
52 const auto customOffsetBeats = (customOffsetMs / 1000.0) * bps;
55 const auto outputLatencyPhase = outputLatencyBeats / numerator;
56 const auto customOffsetPhase = customOffsetBeats / numerator;
58 auto localPhase = (localBarsAndBeatsPos.beats.inBeats() / numerator)
59 - outputLatencyPhase + customOffsetPhase;
60 localPhase = negativeAwareFmod (localPhase, 1.0);
62 const auto linkPhase =
getBarPhase (numerator) / numerator;
63 linkBarPhase = linkPhase;
67 auto offsetPhase = circularDifference (linkPhase, localPhase);
68 chaseProportion = offsetPhase;
70 const auto offsetBeats = offsetPhase * numerator;
74 if (std::abs (offsetBeats) >= (numerator / 2.0) && timeNow > inhibitTimer)
76 inhibitTimer = timeNow + 250;
78 setSpeedCompensation (0.0);
83 setSpeedCompensation (offsetPhase);
87 void timerCallback()
override
91 void setTempoFromLink (
double bpm)
93 bpm = getTempoInRange (bpm);
95 juce::MessageManager::callAsync ([
this, editRef =
makeSafeRef (transport.
edit), bpm]
97 if (editRef != nullptr)
99 inhibitTimer = juce::Time::getMillisecondCounter() + 100;
100 listeners.call (&Listener::linkRequestedTempoChange, bpm);
105 void setStartStopFromLink (
bool isPlaying)
107 juce::MessageManager::callAsync ([
this, editRef =
makeSafeRef (transport.edit), isPlaying]
109 if (editRef != nullptr)
111 inhibitTimer = juce::Time::getMillisecondCounter() + 100;
112 listeners.call (&Listener::linkRequestedStartStopChange, isPlaying);
117 void callConnectionChanged()
119 juce::MessageManager::callAsync ([
this, editRef =
makeSafeRef (transport.edit)]
121 if (editRef != nullptr)
122 listeners.call (&AbletonLink::Listener::linkConnectionChanged);
126 double getBeatsUntilNextCycle (
double quantum)
128 return quantum - getBarPhase (quantum);
131 void setCustomOffset (
int offsetMs)
133 customOffsetMs = offsetMs;
138 allowedTempos = minMaxTempo;
141 setTempoFromLink (getTempoFromLink());
144 double getTempoInRange (
double bpm)
const
149 const auto isTooQuick = [topBpm = allowedTempos.getEnd()] (
double b) {
return b >= topBpm; };
150 const bool wasTooQuick = isTooQuick (bpm);
152 while (isTempoOutOfRange (bpm) && (wasTooQuick == isTooQuick (bpm)))
153 bpm *= wasTooQuick ? 0.5 : 2.0;
155 jassert (! isTempoOutOfRange (bpm));
157 return allowedTempos.clipValue (bpm);
160 bool isTempoOutOfRange (
double bpm)
const
162 return ! (allowedTempos.getStart() <= bpm && bpm <= allowedTempos.getEnd());
165 virtual bool isEnabled()
const = 0;
166 virtual void setEnabled (
bool) = 0;
167 virtual bool isPlaying()
const = 0;
168 virtual void enableStartStopSync (
bool) = 0;
169 virtual bool getStartStopSyncEnabledFromLink()
const = 0;
170 virtual void setStartStopToLink (
bool) = 0;
171 virtual void setTempoToLink (
double bpm) = 0;
172 virtual double getTempoFromLink() = 0;
173 virtual double getBeatNow (
double quantum) = 0;
174 virtual double getBarPhase (
double quantum) = 0;
176 void addListener (Listener* l) { listeners.add (l); }
177 void removeListener (Listener* l) { listeners.remove (l); }
179 static inline double negativeAwareFmod (
double a,
double b)
184 static inline double circularDifference (
double a,
double b)
188 if (diff < -0.5)
return diff + 1.0;
189 if (diff > 0.5)
return diff - 1.0;
194 void setSpeedCompensation (
double phaseProportion)
196 if (
auto epc = transport.getCurrentPlaybackContext())
197 epc->setTempoAdjustment (phaseProportion * 10.0);
200 TransportControl& transport;
212#if TRACKTION_ENABLE_ABLETON_LINK
214#if (JUCE_WINDOWS || JUCE_MAC || JUCE_LINUX || JUCE_ANDROID)
216 struct LinkImpl :
public AbletonLink::ImplBase
218 LinkImpl (TransportControl& t)
219 : AbletonLink::ImplBase (t)
221 link.setNumPeersCallback ([
this] (
std::size_t n) { numPeersCallback (n); });
222 link.setTempoCallback ([
this] (
double bpm) { setTempoFromLink (bpm); });
223 link.setStartStopCallback ([
this] (
bool isPlaying) { setStartStopFromLink (isPlaying); });
229 link.setTempoCallback ([] (
double) {});
230 link.setStartStopCallback ([] (
bool) {});
233 bool isEnabled()
const override
235 TRACKTION_ASSERT_MESSAGE_THREAD
236 return link.isEnabled();
239 void setEnabled (
bool isEnabled)
override
241 TRACKTION_ASSERT_MESSAGE_THREAD
242 link.enable (isEnabled);
243 activateTimer (isEnabled);
246 setTempoFromLink (
link.captureAppSessionState().tempo());
249 bool isPlaying()
const override
251 TRACKTION_ASSERT_MESSAGE_THREAD
252 return link.captureAppSessionState().isPlaying();
255 void enableStartStopSync (
bool enable)
override
257 TRACKTION_ASSERT_MESSAGE_THREAD
258 link.enableStartStopSync (enable);
261 bool getStartStopSyncEnabledFromLink()
const override
263 TRACKTION_ASSERT_MESSAGE_THREAD
264 return link.isStartStopSyncEnabled();
269 jassert (! juce::MessageManager::existsAndIsCurrentThread());
270 numPeers = newNumPeers;
271 callConnectionChanged();
274 setTempoFromLink (
link.captureAudioSessionState().tempo());
277 void setStartStopToLink (
bool isPlaying)
override
279 TRACKTION_ASSERT_MESSAGE_THREAD
280 auto state =
link.captureAppSessionState();
281 state.setIsPlaying (isPlaying,
clock.micros());
282 link.commitAppSessionState (state);
285 void setTempoToLink (
double bpm)
override
287 TRACKTION_ASSERT_MESSAGE_THREAD
288 auto state =
link.captureAppSessionState();
289 state.setTempo (bpm,
clock.micros());
290 link.commitAppSessionState (state);
293 double getTempoFromLink()
override
295 jassert (! juce::MessageManager::existsAndIsCurrentThread());
296 return link.captureAudioSessionState().tempo();
299 double getBeatNow (
double quantum)
override
301 jassert (! juce::MessageManager::existsAndIsCurrentThread());
302 return link.captureAudioSessionState().beatAtTime (
clock.micros(), quantum);
305 double getBarPhase (
double quantum)
override
307 if (juce::MessageManager::existsAndIsCurrentThread())
308 return link.captureAppSessionState().phaseAtTime (
clock.micros(), quantum);
310 return link.captureAudioSessionState().phaseAtTime (
clock.micros(), quantum);
313 ableton::Link::Clock
clock;
314 ableton::Link
link { 120 };
324 struct LinkImpl :
public AbletonLink::ImplBase
326 LinkImpl (TransportControl& t)
327 : AbletonLink::ImplBase (t),
328 link (ABLLinkNew (120))
330 ABLLinkSetSessionTempoCallback (link, tempoChangedCallback,
this);
331 ABLLinkSetIsConnectedCallback (link, isConnectedCallback,
this);
332 ABLLinkSetIsEnabledCallback (link, isEnabledCallback,
this);
333 ABLLinkSetStartStopCallback (link, startStopCallback,
this);
335 setEnabled (isActive);
340 ABLLinkDelete (link);
343 bool isEnabled()
const override
345 TRACKTION_ASSERT_MESSAGE_THREAD
346 return ABLLinkIsEnabled (link) && isActive;
349 void setEnabled (
bool isEnabled)
override
351 TRACKTION_ASSERT_MESSAGE_THREAD
352 isActive = isEnabled;
353 ABLLinkSetActive (link, isEnabled);
357 Timer::callAfterDelay (500, [
this, editRef = makeSafeRef (transport.edit)]
359 if (editRef != nullptr)
360 isConnectedCallback (ABLLinkIsConnected (link), this);
364 bool isPlaying()
const override
366 TRACKTION_ASSERT_MESSAGE_THREAD
367 return ABLLinkIsPlaying (ABLLinkCaptureAppSessionState (link));
370 void enableStartStopSync (
bool)
override
375 bool getStartStopSyncEnabledFromLink()
const override
377 TRACKTION_ASSERT_MESSAGE_THREAD
378 return ABLLinkIsStartStopSyncEnabled (link);
381 void setStartStopToLink (
bool isPlaying)
override
383 TRACKTION_ASSERT_MESSAGE_THREAD
384 auto state = ABLLinkCaptureAppSessionState (link);
386 ABLLinkCommitAppSessionState (link, state);
389 void setTempoToLink (
double bpm)
override
391 TRACKTION_ASSERT_MESSAGE_THREAD
392 auto state = ABLLinkCaptureAppSessionState (link);
394 ABLLinkCommitAppSessionState (link, state);
397 double getTempoFromLink()
override
399 jassert (! juce::MessageManager::existsAndIsCurrentThread());
400 return ABLLinkGetTempo (ABLLinkCaptureAudioSessionState (link));
403 double getBeatNow (
double quantum)
override
405 jassert (! juce::MessageManager::existsAndIsCurrentThread());
406 auto state = ABLLinkCaptureAudioSessionState (link);
410 double getBarPhase (
double quantum)
override
413 ? ABLLinkCaptureAppSessionState (link)
414 : ABLLinkCaptureAudioSessionState (
link);
419 static void tempoChangedCallback (
double bpm,
void *context)
421 auto* thisPtr =
static_cast<LinkImpl*
> (context);
422 thisPtr->setTempoFromLink (bpm);
425 static void isConnectedCallback (
bool isConnected,
void *context)
427 auto* thisPtr =
static_cast<LinkImpl*
> (context);
429 thisPtr->numPeers = (
size_t) (isConnected ? 1 : 0);
430 thisPtr->activateTimer (isConnected);
432 isEnabledCallback (isConnected, context);
435 static void isEnabledCallback (
bool isEnabled,
void *context)
437 auto* thisPtr =
static_cast<LinkImpl*
> (context);
440 thisPtr->numPeers = (
size_t) 0;
442 thisPtr->callConnectionChanged();
445 broadcastTempo (thisPtr);
448 static void startStopCallback (
bool isPlaying,
void *context)
450 auto* thisPtr =
static_cast<LinkImpl*
> (context);
451 thisPtr->setStartStopFromLink (isPlaying);
454 static void broadcastTempo (LinkImpl* context)
456 context->setTempoFromLink (context->getTempoFromLink());
460 static bool isActive;
463 bool LinkImpl::isActive =
true;
472 #if TRACKTION_ENABLE_ABLETON_LINK
479AbletonLink::~AbletonLink() {}
481#if TRACKTION_ENABLE_ABLETON_LINK && JUCE_IOS
482 ABLLink* AbletonLink::getLinkInstanceForIOS() {
return static_cast<LinkImpl*
>(implementation.get())->link; }
485void AbletonLink::setEnabled (
bool isEnabled)
487 if (implementation !=
nullptr)
488 implementation->setEnabled (isEnabled);
491bool AbletonLink::isEnabled()
const
493 return implementation !=
nullptr && implementation->isEnabled();
496bool AbletonLink::isConnected()
const
498 return implementation !=
nullptr && implementation->isConnected();
501size_t AbletonLink::getNumPeers()
const
503 return implementation !=
nullptr ? implementation->numPeers.load() : 0;
506bool AbletonLink::isPlaying()
const
508 return implementation !=
nullptr && implementation->isPlaying();
511void AbletonLink::enableStartStopSync (
bool enable)
513 if (implementation !=
nullptr)
514 implementation->enableStartStopSync (enable);
517bool AbletonLink::isStartStopSyncEnabled()
const
519 return implementation !=
nullptr && implementation->getStartStopSyncEnabledFromLink();
522double AbletonLink::getBeatsUntilNextCycle (
double quantum)
const
524 if (implementation !=
nullptr)
525 return implementation->getBeatsUntilNextCycle (quantum);
530void AbletonLink::requestStartStopChange (
bool isPlaying)
532 if (implementation !=
nullptr)
533 implementation->setStartStopToLink (isPlaying);
536void AbletonLink::requestTempoChange (
double newBpm)
538 if (implementation !=
nullptr)
539 implementation->setTempoToLink (newBpm);
544 if (implementation !=
nullptr)
545 implementation->setTempoConstraint (minMaxTempo);
548double AbletonLink::getBarPhase()
const
550 return implementation !=
nullptr ? implementation->linkBarPhase.load() : 0.0;
553double AbletonLink::getChaseProportion()
const
555 return implementation !=
nullptr ? implementation->chaseProportion.load() : 0.0;
558double AbletonLink::getSessionTempo()
const
560 if (implementation !=
nullptr)
561 return implementation->getTempoFromLink();
566void AbletonLink::setCustomOffset (
int offsetMs)
568 if (implementation !=
nullptr)
569 implementation->setCustomOffset (offsetMs);
574 if (implementation !=
nullptr)
575 implementation->syncronise (pos);
580 if (implementation !=
nullptr)
581 implementation->addListener (l);
586 if (implementation !=
nullptr)
587 implementation->removeListener (l);
static bool existsAndIsCurrentThread() noexcept
static int64 getHighResolutionTicks() noexcept
static uint32 getMillisecondCounter() noexcept
void stopTimer() noexcept
void startTimer(int intervalInMilliseconds) noexcept
double getBarPhase() const
Returns how far though the current bar the Link clock is.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
const tempo::Sequence & getInternalSequence() const
N.B.
Controls the transport of an Edit's playback.
bool isPlaying() const
Returns true if the transport is playing.
Edit & edit
The Edit this transport belongs to.
Engine & engine
The Engine this Edit belongs to.
void ignoreUnused(Types &&...) noexcept
SafeSelectable< SelectableType > makeSafeRef(SelectableType &selectable)
Creates a SafeSelectable for a given selectable object.
Represents a position in real-life time.
When Link is on, you shouldn't set the tempo directly.
virtual void linkRequestedPositionChange(BeatDuration adjustment)=0
Should be implemented to perform a larger beat position change.