11namespace tracktion {
inline namespace engine
16 #define DECLARE_ID(name) const juce::Identifier name (#name);
18 DECLARE_ID (safeRecording)
19 DECLARE_ID (discardRecordings)
20 DECLARE_ID (clearDevices)
21 DECLARE_ID (justSendMMCIfEnabled)
22 DECLARE_ID (canSendMMCStop)
23 DECLARE_ID (allowRecordingIfNoInputsArmed)
24 DECLARE_ID (clearDevicesOnStop)
25 DECLARE_ID (updatingFromPlayHead)
26 DECLARE_ID (scrubInterval)
28 DECLARE_ID (userDragging)
29 DECLARE_ID (lastUserDragTime)
30 DECLARE_ID (reallocationInhibitors)
31 DECLARE_ID (playbackContextAllocation)
33 DECLARE_ID (rewindButtonDown)
34 DECLARE_ID (fastForwardButtonDown)
35 DECLARE_ID (nudgeLeftCount)
36 DECLARE_ID (nudgeRightCount)
38 DECLARE_ID (videoPosition)
39 DECLARE_ID (forceVideoJump)
44namespace TransportHelpers
46 inline TimePosition snapTime (TransportControl& tc, TimePosition t,
bool invertSnap)
48 return (tc.snapToTimecode ^ invertSnap) ? tc.getSnapType().roundTimeNearest (t, tc.edit.tempoSequence)
52 inline TimePosition snapTimeUp (TransportControl& tc, TimePosition t,
bool invertSnap)
54 return (tc.snapToTimecode ^ invertSnap) ? tc.getSnapType().roundTimeUp (t, tc.edit.tempoSequence)
58 inline TimePosition snapTimeDown (TransportControl& tc, TimePosition t,
bool invertSnap)
60 return (tc.snapToTimecode ^ invertSnap) ? tc.getSnapType().roundTimeDown (t, tc.edit.tempoSequence)
66 auto epc = tc.getCurrentPlaybackContext();
74 edit.clipSlotCache.visitItems ([&] (
auto cs)
76 if (
auto c = cs->getClip())
78 if (auto lh = c->getLaunchHandle())
80 if (lh->getQueuedStatus() == LaunchHandle::QueueState::stopQueued)
83 if (lh->getPlayingStatus() == LaunchHandle::PlayState::playing)
84 launchedClips.add (c);
95 for (
auto c : launchedClips)
96 c->getLaunchHandle()->stop ({});
98 epc->blockUntilSyncPointChange();
101 const auto currentPoint = epc->getSyncPoint();
107 startPoint = currentPoint;
109 auto& ts = edit.tempoSequence;
110 const auto currentBeat = ts.toBeats (tc.getPosition());
112 MonotonicBeat startSyncBeat;
114 if (syncToStartOfBar)
116 const auto currentBarsBeats = ts.toBarsAndBeats (tc.getPosition());
117 const auto barStartBeat = ts.toBeats (tempo::BarsAndBeats { .bars = currentBarsBeats.bars });
118 const auto launchBeatDiff = currentBeat - barStartBeat;
119 startSyncBeat = MonotonicBeat { currentPoint->monotonicBeat.v - launchBeatDiff };
123 const auto launchBeatDiff = currentPoint->beat - startPoint->beat;
124 startSyncBeat = MonotonicBeat { currentPoint->monotonicBeat.v - launchBeatDiff };
127 for (
auto c : launchedClips)
129 auto lh = c->getLaunchHandle();
131 lh->play (startSyncBeat);
144 : state (transportStateToUse), transport (tc)
148 playing.referTo (transientState, IDs::playing, um);
149 recording.referTo (transientState, IDs::recording, um);
150 safeRecording.referTo (transientState, IDs::safeRecording, um);
152 discardRecordings.referTo (transientState, IDs::discardRecordings, um);
153 clearDevices.referTo (transientState, IDs::clearDevices, um);
154 justSendMMCIfEnabled.referTo (transientState, IDs::justSendMMCIfEnabled, um);
155 canSendMMCStop.referTo (transientState, IDs::canSendMMCStop, um);
156 allowRecordingIfNoInputsArmed.referTo (transientState, IDs::allowRecordingIfNoInputsArmed, um);
157 clearDevicesOnStop.referTo (transientState, IDs::clearDevicesOnStop, um);
158 updatingFromPlayHead.referTo (transientState, IDs::updatingFromPlayHead, um);
160 startTime.referTo (transientState, IDs::startTime, um);
161 endTime.referTo (transientState, IDs::endTime, um);
162 userDragging.referTo (transientState, IDs::userDragging, um);
163 lastUserDragTime.referTo (transientState, IDs::lastUserDragTime, um);
164 reallocationInhibitors.referTo (transientState, IDs::reallocationInhibitors, um);
165 playbackContextAllocation.referTo (transientState, IDs::playbackContextAllocation, um);
167 rewindButtonDown.referTo (transientState, IDs::rewindButtonDown, um);
168 fastForwardButtonDown.referTo (transientState, IDs::fastForwardButtonDown, um);
169 nudgeLeftCount.referTo (transientState, IDs::nudgeLeftCount, um);
170 nudgeRightCount.referTo (transientState, IDs::nudgeRightCount, um);
172 videoPosition.referTo (transientState, IDs::videoPosition, um);
173 forceVideoJump.referTo (transientState, IDs::forceVideoJump, um);
177 playing = playing.get();
178 recording = recording.get();
179 safeRecording = safeRecording.get();
181 state.addListener (
this);
182 transientState.addListener (
this);
188 jassert (reallocationInhibitors == 0);
194 forceVideoJump = forceJump;
195 videoPosition =
time;
200 void play (
bool justSendMMCIfEnabled_)
202 justSendMMCIfEnabled = justSendMMCIfEnabled_;
207 void record (
bool justSendMMCIfEnabled_,
bool allowRecordingIfNoInputsArmed_)
209 justSendMMCIfEnabled = justSendMMCIfEnabled_;
210 allowRecordingIfNoInputsArmed = allowRecordingIfNoInputsArmed_;
215 void stop (
bool discardRecordings_,
217 bool canSendMMCStop_)
219 discardRecordings = discardRecordings_;
220 clearDevices = clearDevices_;
221 canSendMMCStop = canSendMMCStop_;
225 void updatePositionFromPlayhead (
TimePosition newPosition)
227 updatingFromPlayHead =
true;
228 state.setProperty (IDs::position, newPosition.
inSeconds(),
nullptr);
229 updatingFromPlayHead =
false;
234 nudgeLeftCount = ((nudgeLeftCount + 1) % 2);
239 nudgeRightCount = ((nudgeRightCount + 1) % 2);
245 allowRecordingIfNoInputsArmed, clearDevicesOnStop;
246 juce::CachedValue<bool> userDragging, forceVideoJump, rewindButtonDown, fastForwardButtonDown, updatingFromPlayHead;
250 juce::CachedValue<int> reallocationInhibitors, playbackContextAllocation, nudgeLeftCount, nudgeRightCount;
253 TransportControl& transport;
256 bool isInsideRecordingCallback =
false;
262 if (i == IDs::position)
264 if (! updatingFromPlayHead)
265 transport.performPositionChange();
267 else if (i == IDs::looping)
269 transport.stopIfRecording();
271 auto& ecm = transport.engine.getExternalControllerManager();
273 if (ecm.isAttachedToEdit (transport.edit))
274 ecm.loopChanged (state[IDs::looping]);
276 else if (i == IDs::snapToTimecode)
278 auto& ecm = transport.engine.getExternalControllerManager();
280 if (ecm.isAttachedToEdit (transport.edit))
281 ecm.snapChanged (state[IDs::snapToTimecode]);
284 else if (v == transientState)
286 if (i == IDs::playing)
288 playing.forceUpdateOfCachedValue();
291 transport.performPlay();
293 transport.performStop();
295 transport.startedOrStopped();
297 else if (i == IDs::recording)
301 if (isInsideRecordingCallback)
304 recording.forceUpdateOfCachedValue();
310 if (
auto res = transport.performRecord())
313 transport.listeners.call (&TransportControl::Listener::recordingStarted, res->first, res->second);
322 transport.performStopRecording();
325 else if (i == IDs::playbackContextAllocation)
327 transport.listeners.call (&TransportControl::Listener::playbackContextChanged);
329 else if (i == IDs::videoPosition)
331 videoPosition.forceUpdateOfCachedValue();
332 transport.listeners.call (&TransportControl::Listener::setVideoPosition, videoPosition.get(), forceVideoJump);
334 else if (i == IDs::rewindButtonDown)
336 fastForwardButtonDown =
false;
337 rewindButtonDown.forceUpdateOfCachedValue();
338 transport.performRewindButtonChanged();
340 else if (i == IDs::fastForwardButtonDown)
342 rewindButtonDown =
false;
343 fastForwardButtonDown.forceUpdateOfCachedValue();
344 transport.performFastForwardButtonChanged();
346 else if (i == IDs::nudgeLeftCount)
348 transport.performNudgeLeft();
350 else if (i == IDs::nudgeRightCount)
352 transport.performNudgeRight();
362 : transport (tc), section (sectionToPlay),
363 wasLooping (tc.looping)
365 jassert (! sectionToPlay.isEmpty());
366 transport.setPosition (sectionToPlay.getStart());
367 transport.looping =
false;
368 transport.play (
false);
376 transport.looping =
true;
380 const TimeRange section;
381 const bool wasLooping;
383 void timerCallback()
override
385 if (transport.getPosition() > section.getEnd())
386 transport.stop (
false,
false);
399 void timerCallback()
override
401 if (owner.edit.isLoading())
406 if (active && forcePurge)
408 hasBeenDeactivated =
true;
412 auto canPurge = [
this]
414 if (owner.isPlaying() || owner.isRecording())
417 return SmartThumbnail::areThumbnailsFullyLoaded (owner.engine);
420 if (active != hasBeenDeactivated
423 hasBeenDeactivated = active;
428 owner.engine.getAudioFileManager().releaseAllFiles();
430 TemporaryFileManager::purgeOrphanFreezeAndProxyFiles (owner.edit);
435 owner.engine.getAudioFileManager().checkFilesForChanges();
441 bool hasBeenDeactivated =
false, forcePurge =
false;
448 : owner (tc), isRewind (isRW)
452 void setDown (
bool b)
467 static int buttsDown = 0;
481 owner.setUserDragging (buttsDown > 0);
495 bool isRewind, isDown =
false, firstPress =
false;
498 void timerCallback()
override
501 double secs = (now - lastClickTime).inSeconds();
507 if (owner.ffRepeater->isDown)
513 if (owner.snapToTimecode)
524 t = TransportHelpers::snapTimeDown (owner, t - 1.0e-5s,
false);
526 t = TransportHelpers::snapTimeUp (owner, t + 1.0e-5s,
false);
536 accel =
std::min (accel + 0.1, 6.0);
538 scrub (owner, secs * 10.0);
553 return transport.playbackContext ? transport.playbackContext->getNodePlayHead()
557 double getSampleRate()
const
559 return transport.playbackContext ? transport.playbackContext->getSampleRate()
565 if (
auto ph = getNodePlayHead())
569 void play (TimeRange timeRange,
bool looped)
571 if (
auto ph = getNodePlayHead())
572 ph->play (tracktion::toSamples (timeRange, getSampleRate()), looped);
577 if (
auto ph = getNodePlayHead())
578 ph->setRollInToLoop (tracktion::toSamples (prerollStartTime, getSampleRate()));
583 if (
auto ph = getNodePlayHead())
587 bool isPlaying()
const
589 if (
auto ph = getNodePlayHead())
590 return ph->isPlaying();
598 if (getNodePlayHead() !=
nullptr && transport.playbackContext !=
nullptr && transport.playbackContext->isPlaybackGraphAllocated())
599 return transport.playbackContext->getAudibleTimelineTime();
601 return getPosition();
606 if (
auto ph = getNodePlayHead())
607 return TimePosition::fromSamples (ph->getPosition(), getSampleRate());
614 if (
auto ph = getNodePlayHead())
615 return TimePosition::fromSamples (ph->getUnloopedPosition(), getSampleRate());
620 void setPosition (TimePosition newPos)
622 if (getNodePlayHead() !=
nullptr)
623 transport.playbackContext->postPosition (newPos);
626 bool isLooping()
const
628 if (
auto ph = getNodePlayHead())
629 return ph->isLooping();
636 if (
auto ph = getNodePlayHead())
642 void setLoopTimes (
bool loop, TimeRange newRange)
644 if (
auto ph = getNodePlayHead())
645 ph->setLoopRange (loop, tracktion::toSamples (newRange, getSampleRate()));
648 void setUserIsDragging (
bool isDragging)
650 if (
auto ph = getNodePlayHead())
651 ph->setUserIsDragging (isDragging);
655 TransportControl& transport;
670int TransportControl::getNumPlayingTransports (
Engine& engine)
675void TransportControl::stopAllTransports (
Engine& engine,
bool discardRecordings,
bool clearDevices)
678 tc->
stop (discardRecordings, clearDevices);
692 tc->
stop (
false,
true);
703 return restartHandles;
713 recordingIsStoppingFlag =
true;
717void TransportControl::callRecordingFinishedListeners (InputDeviceInstance& in, EditItemID targetID, Clip::Array recordedClips)
719 recordingIsStoppingFlag =
false;
723TransportControl::PlayingFlag::PlayingFlag (
Engine& e) noexcept : engine (e) { ++engine.getActiveEdits().numTransportsPlaying; }
724TransportControl::PlayingFlag::~PlayingFlag() noexcept { --engine.getActiveEdits().numTransportsPlaying; }
727void TransportControl::editHasChanged()
729 if (transportState->reallocationInhibitors > 0)
731 isDelayedChangePending =
true;
735 isDelayedChangePending =
false;
737 if (playbackContext ==
nullptr)
744bool TransportControl::isAllowedToReallocate() const noexcept
746 return transportState->reallocationInhibitors <= 0;
753 auto& inhibitors = transport.transportState->reallocationInhibitors;
754 inhibitors = inhibitors + 1;
759 auto& inhibitors = transport.transportState->reallocationInhibitors;
761 inhibitors =
std::max (0, inhibitors - 1);
766void TransportControl::releaseAudioNodes()
768 if (playbackContext !=
nullptr)
769 playbackContext->clearNodes();
777 const auto start = position.get();
779 if (playbackContext ==
nullptr)
782 playbackContext->createPlayAudioNodes (start);
783 transportState->playbackContextAllocation = transportState->playbackContextAllocation + 1;
786 if (alwaysReallocate)
787 playbackContext->createPlayAudioNodes (start);
789 playbackContext->createPlayAudioNodesIfNeeded (start);
794 playbackContext.reset();
796 transportState->playbackContextAllocation =
std::max (0, transportState->playbackContextAllocation - 1);
801 transportState->clearDevicesOnStop =
true;
812 fileFlushTimer->forcePurge =
true;
816static int numScreenSaverDefeaters = 0;
825 TRACKTION_ASSERT_MESSAGE_THREAD
826 ++numScreenSaverDefeaters;
835 TRACKTION_ASSERT_MESSAGE_THREAD
836 --numScreenSaverDefeaters;
837 jassert (numScreenSaverDefeaters >= 0);
852 position.referTo (
state, IDs::position, um);
853 loopPoint1.referTo (
state, IDs::loopPoint1, um);
854 loopPoint2.referTo (
state, IDs::loopPoint2, um);
855 snapToTimecode.
referTo (
state, IDs::snapToTimecode, um,
true);
857 scrubInterval.referTo (
state, IDs::scrubInterval, um, 0.1s);
867 activeTransportControls.add (
this);
873 activeTransportControls.removeAllInstancesOf (
this);
874 fileFlushTimer =
nullptr;
883 transportState->play (justSendMMCIfEnabled);
889 TransportHelpers::resyncLauncherClips (*
this, {},
true);
890 play (justSendMMCIfEnabled);
903 transportState->record (justSendMMCIfEnabled, allowRecordingIfNoInputsArmed);
910 transportState->stop (discardRecordings,
926 transportState->discardRecordings = discardRecordings;
927 transportState->recording =
false;
938 return playbackContext->applyRetrospectiveRecord (&clips, armedOnly);
953 playbackContext->applyRetrospectiveRecord (&clips);
955 if (clips.
size() > 0)
961 auto f = ac->getOriginalFile();
964 else if (
auto mc =
dynamic_cast<MidiClip*
> (c))
966 auto clipPos = mc->getPosition();
969 clipsToRender.
add (mc);
980 if (mc->getTrack() == t)
987 tracksToDo,
true,
false, clipsToRender,
true);
992 c->removeFromParent();
1005 if (playbackContext && editToSyncTo !=
nullptr)
1010 auto& tempo = tempoSequence.
getTempoAt (position);
1011 auto& timeSig = tempoSequence.getTimeSigAt (position);
1013 auto barsBeats = tempoSequence.toBarsAndBeats (targetContext->isLooping()
1014 ? targetContext->getLoopTimes().getStart()
1017 auto previousBarTime = tempoSequence.toTime ({ barsBeats.bars, {} });
1019 auto syncInterval = isPreview ? targetContext->getLoopTimes().getLength()
1020 : TimeDuration::fromSeconds ((60.0 / tempo.getBpm() * timeSig.numerator));
1022 playbackContext->syncToContext (targetContext, previousBarTime, syncInterval);
1037bool TransportControl::areAnyInputsRecording()
1039 for (
auto in :
edit.getAllInputDevices())
1040 if (in->isRecordingActive())
1046void TransportControl::clearPlayingFlags()
1048 transportState->playing =
false;
1049 transportState->recording =
false;
1050 transportState->safeRecording =
false;
1051 playingFlag.
reset();
1055void TransportControl::timerCallback()
1059 if (playbackContext ==
nullptr)
1062 if (isDelayedChangePending)
1065 if (
isPlaying() && playHeadWrapper->getPosition() >= Edit::getMaximumEditEnd())
1067 stop (
false,
false);
1068 position = Edit::getMaximumEditEnd();
1072 if (! playHeadWrapper->isPlaying())
1076 stop (
false,
false);
1081 stop (
false,
false);
1091 && (! transportState->userDragging)
1099 const auto currentTime = playHeadWrapper->getLiveTransportPosition();
1100 transportState->setVideoPosition (currentTime,
false);
1101 transportState->updatePositionFromPlayhead (currentTime);
1106 if (--loopUpdateCounter == 0)
1108 loopUpdateCounter = 10;
1113 lr = lr.withEnd (
std::max (lr.getEnd(), lr.getStart() + 0.001s));
1114 playHeadWrapper->setLoopTimes (
true, lr);
1118 playHeadWrapper->setLoopTimes (
false, {});
1127 sectionPlayer.reset();
1128 transportState->rewindButtonDown = isDown;
1133 sectionPlayer.reset();
1134 transportState->fastForwardButtonDown = isDown;
1139 sectionPlayer.reset();
1140 transportState->nudgeLeft();
1145 sectionPlayer.reset();
1146 transportState->nudgeRight();
1153 return position.get();
1167 epc->postPosition (timeToMoveTo, timeToPerformJump);
1176 if (playbackContext !=
nullptr)
1177 playHeadWrapper->setUserIsDragging (b);
1179 if (b != transportState->userDragging)
1181 if (transportState->userDragging &&
isPlaying())
1185 if (playbackContext !=
nullptr)
1186 playHeadWrapper->setPosition (position);
1189 transportState->userDragging = b;
1198 return transportState->userDragging;
1203 return transportState->updatingFromPlayHead;
1239 return TimeRange::between (loopPoint1, loopPoint2);
1244 currentSnapType = newSnapType;
1249void TransportControl::startedOrStopped()
1253 const bool wasRecording = lastRecordStatus;
1269 transportState->setVideoPosition (
getPosition(),
true);
1291 for (
int i = dm.getNumMidiOutDevices(); --i >= 0;)
1293 if (
auto* mo = dm.getMidiOutDevice (i))
1295 if (mo->isEnabled() && mo->isSendingMMC())
1297 mo->fireMessage (mmc);
1309inline bool anyEnabledMidiOutDevicesSendingMMC (DeviceManager& dm)
1311 for (
int i = dm.getNumMidiOutDevices(); --i >= 0;)
1312 if (
auto mo = dm.getMidiOutDevice (i))
1313 if (mo->isEnabled() && mo->isSendingMMC())
1319bool TransportControl::sendMMCStartPlay()
1323 sendMMCCommand (juce::MidiMessage::mmc_play);
1332bool TransportControl::sendMMCStartRecord()
1336 sendMMCCommand (juce::MidiMessage::mmc_recordStart);
1346void TransportControl::performPlay()
1349 sectionPlayer.reset();
1356 if (transportState->justSendMMCIfEnabled && sendMMCStartPlay())
1361 const auto cursorPos = position.get();
1364 if (cursorPos < loopRange.getStart()
1365 || cursorPos > loopRange.getEnd() - 0.1s)
1367 position = loopRange.getStart();
1370 transportState->startTime = loopRange.getStart();
1371 transportState->endTime = loopRange.getEnd();
1373 if (transportState->endTime < transportState->startTime + 0.01s)
1381 transportState->startTime = position.get();
1382 transportState->endTime = Edit::getMaximumEditEnd();
1390 const double cyclePos =
std::fmod (transportState->startTime.get().inSeconds(), barLength);
1393 transportState->startTime = TimePosition::fromSeconds ((transportState->startTime.get().inSeconds() - cyclePos) + (barLength - nextLinkCycle));
1396 transportState->recording =
false;
1397 transportState->safeRecording =
false;
1402 if (playbackContext)
1404 playHeadWrapper->play ({ transportState->startTime, transportState->endTime }, looping);
1409 playHeadWrapper->setPosition (position);
1413 clearPlayingFlags();
1426 sectionPlayer.reset();
1430 if (! transportState->userDragging)
1432 if (transportState->justSendMMCIfEnabled && sendMMCStartRecord())
1435 if (transportState->allowRecordingIfNoInputsArmed || areAnyInputsRecording())
1440 assert (playbackContext);
1442 punchInPoint = playbackContext->getSyncPoint();
1447 const auto currentPos = playbackContext->getPosition();
1450 playbackContext->prepareForRecording (currentPos, punchInTime);
1458 transportState->startTime = position.get();
1459 transportState->endTime = Edit::getMaximumEditEnd();
1463 if (loopRange.getLength() < 2s)
1475 transportState->startTime = loopRange.getStart();
1479 if ((loopRange.getEnd() + 0.1s) <= transportState->startTime)
1480 transportState->startTime = (loopRange.getStart() - 1.0s);
1484 if (abs (transportState->startTime) < 0.005s)
1485 transportState->startTime = 0s;
1488 auto prerollStart = transportState->startTime.get();
1492 if (numCountInBeats > 0)
1494 auto currentBeat = ts.
toBeats (transportState->startTime);
1495 prerollStart = ts.toTime (currentBeat - BeatDuration::fromBeats (numCountInBeats + 0.5));
1502 double barLength = ts.getTimeSig (0)->numerator;
1505 if (numCountInBeats > 0)
1506 beatsUntilNextLinkCycle -= 0.5;
1508 prerollStart = prerollStart -
toDuration (ts.toTime (BeatPosition::fromBeats (beatsUntilNextLinkCycle)));
1518 if (playbackContext)
1521 playHeadWrapper->setLoopTimes (
true, { transportState->startTime.get(), Edit::getMaximumEditEnd() });
1526 if (prerollStart < 0.2s)
1527 prerollStart = prerollStart - 0.2s;
1534 lr = lr.withEnd (
std::max (lr.getEnd(), lr.getStart() + 0.001s));
1535 playHeadWrapper->setLoopTimes (
true, lr);
1536 playHeadWrapper->setRollInToLoop (prerollStart);
1542 playHeadWrapper->setLoopTimes (
false, { prerollStart, transportState->endTime.get() });
1545 playHeadWrapper->setPosition (prerollStart);
1546 position = prerollStart;
1550 playbackContext->blockUntilSyncPointChange();
1555 const auto currentSyncPoint = playbackContext->getSyncPoint();
1556 const auto punchInTime = transportState->startTime.get();
1557 const auto punchInBeat = ts.toBeats (punchInTime);
1558 const auto timeNow = currentSyncPoint->time;
1559 const auto beatNow = ts.toBeats (timeNow);
1560 const auto beatsUntilPunchIn = punchInBeat - beatNow;
1561 const auto samplesUntilPunchIn =
toSamples (punchInTime - timeNow, playbackContext->getSampleRate());
1562 punchInPoint = SyncPoint { .referenceSamplePosition = currentSyncPoint->referenceSamplePosition + samplesUntilPunchIn,
1563 .monotonicBeat = { currentSyncPoint->monotonicBeat.v + beatsUntilPunchIn },
1564 .unloopedTime = punchInTime,
1565 .time = transportState->startTime.get(),
1566 .beat = ts.toBeats (transportState->startTime.get()) } ;
1569 TransportHelpers::resyncLauncherClips (*
this, punchInPoint,
false);
1572 playbackContext->prepareForRecording (prerollStart, transportState->startTime.get());
1578 const auto clickStartBeat = ts.toBeats (prerollStart);
1579 const auto clickEndBeat = ts.toBeats (transportState->startTime.get());
1581 edit.
setClickTrackRange (ts.toTime ({ BeatPosition::fromBeats (std::ceil (clickStartBeat.inBeats() + 0.5)),
1582 BeatPosition::fromBeats (std::ceil (clickEndBeat.inBeats())) - 0.5_bd }));
1589 playHeadWrapper->play();
1590 transportState->playing =
true;
1598 TRANS(
"Recording is only possible when at least one active input device is assigned to a track"));
1604 if (! transportState->justSendMMCIfEnabled)
1605 sendMMCCommand (juce::MidiMessage::mmc_recordStart);
1607 if (transportState->safeRecording)
1616 if (! playbackContext)
1625 const bool discardRecordings = transportState->discardRecordings;
1626 const auto syncPoint = playbackContext->getSyncPoint();
1628 playbackContext->stopRecording (syncPoint->unloopedTime, discardRecordings)
1640void TransportControl::performStop()
1645 screenSaverDefeater.reset();
1646 sectionPlayer.reset();
1650 if (playbackContext ==
nullptr)
1653 clearPlayingFlags();
1657 if (! juce::Component::isMouseButtonDownAnywhere())
1665 auto recEndTime = playHeadWrapper->getUnloopedPosition();
1666 auto recEndPos = playHeadWrapper->getPosition();
1668 clearPlayingFlags();
1669 playHeadWrapper->stop();
1670 auto syncPoint = playbackContext->getSyncPoint();
1672 playbackContext->stopRecording (recEndTime, transportState->discardRecordings)
1675 position = transportState->discardRecordings ? transportState->startTime.get()
1676 : (looping ? recEndPos
1683 if (transportState->discardRecordings)
1686 clearPlayingFlags();
1687 playHeadWrapper->stop();
1691 releaseAudioNodes();
1695 transportState->clearDevicesOnStop =
false;
1697 if (transportState->canSendMMCStop)
1698 sendMMCCommand (juce::MidiMessage::mmc_stop);
1701void TransportControl::performPositionChange()
1705 sectionPlayer.reset();
1709 stop (
false,
false);
1711 auto newPos = TimePosition::fromSeconds (
static_cast<double> (
state[IDs::position]));
1716 newPos =
juce::jlimit (range.getStart(), range.getEnd(), newPos);
1721 newPos =
juce::jlimit (minStartTime, Edit::getMaximumEditEnd(), newPos);
1724 if (playbackContext !=
nullptr)
1725 playHeadWrapper->setPosition (newPos);
1731 if (! transportState->userDragging)
1734 transportState->setVideoPosition (newPos,
true);
1737 const double nudge = 0.05 / 96000.0;
1740 const int frames = ((
int) (mmcTime * framesPerSecond)) % framesPerSecond;
1741 const int hours = (
int) (mmcTime * (1.0 / 3600.0));
1742 const int minutes = (((
int) mmcTime) / 60) % 60;
1743 const int seconds = (((
int) mmcTime) % 60);
1748void TransportControl::performRewindButtonChanged()
1750 const bool isDown = transportState->rewindButtonDown;
1751 rwRepeater->setDown (isDown);
1754 sendMMCCommand (juce::MidiMessage::mmc_rewind);
1756 sendMMCCommand (
isPlaying() ? juce::MidiMessage::mmc_play
1757 :
juce::MidiMessage::mmc_stop);
1760void TransportControl::performFastForwardButtonChanged()
1762 const bool isDown = transportState->fastForwardButtonDown;
1763 ffRepeater->setDown (isDown);
1766 sendMMCCommand (juce::MidiMessage::mmc_fastforward);
1768 sendMMCCommand (
isPlaying() ? juce::MidiMessage::mmc_play
1769 :
juce::MidiMessage::mmc_stop);
1772void TransportControl::performNudgeLeft()
1774 rwRepeater->nudge();
1777void TransportControl::performNudgeRight()
1779 ffRepeater->nudge();
1784static TimeRange getLimitsOfSelectedClips (Edit& edit,
const SelectableList& items)
1788 if (range.isEmpty())
1789 return { {}, edit.getLength() };
1796 auto selectionStart = getLimitsOfSelectedClips (tc.
edit, items).getStart();
1802 auto selectionEnd = getLimitsOfSelectedClips (tc.
edit, items).getEnd();
1815 const auto unitSize = tc.scrubInterval.get();
1816 auto timeToMove = unitSize * units;
1819 if (tc.snapToTimecode)
1821 if (timeToMove > 0s)
1822 t = TransportHelpers::snapTimeUp (tc, t,
false);
1824 t = TransportHelpers::snapTimeDown (tc, t,
false);
1828 t = TransportHelpers::snapTimeDown (tc, t,
false);
bool isEmpty() const noexcept
int size() const noexcept
void add(const ElementType &newElement)
BigInteger & setBit(int bitNumber)
void referTo(ValueTree &tree, const Identifier &property, UndoManager *um)
static Desktop &JUCE_CALLTYPE getInstance()
static void setScreenSaverEnabled(bool isEnabled)
static File JUCE_CALLTYPE getSpecialLocation(const SpecialLocationType type)
static String createLegalFileName(const String &fileNameToFix)
MidiMachineControlCommand
static MidiMessage midiMachineControlGoto(int hours, int minutes, int seconds, int frames)
static MidiMessage midiMachineControlCommand(MidiMachineControlCommand command)
static bool JUCE_CALLTYPE isForegroundProcess()
static Result fail(const String &errorMessage) noexcept
static Time JUCE_CALLTYPE getCurrentTime() noexcept
static uint32 getMillisecondCounter() noexcept
void startTimerHz(int timerFrequencyHz) noexcept
bool hasType(const Identifier &typeName) const noexcept
double getBeatsUntilNextCycle(double quantum) const
Number of beats until the next bar (quantum) - e.g.
bool isConnected() const
Is Link connected to any other peers?
The Tracktion Edit class!
void restartPlayback()
Use this to tell the play engine to rebuild the audio graph if the toplogy has changed.
TimeDuration getTimecodeOffset() const noexcept
Returns the offset to apply to MIDI timecode.
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
void sendStartStopMessageToPlugins()
Calls Plugin::playStartedOrStopped to handle automation reacording.
TimecodeDisplayFormat getTimecodeFormat() const
Returns the current TimecodeDisplayFormat.
void setClickTrackRange(TimeRange) noexcept
Sets a range for the click track to be audible within.
TimeDuration getLength() const
Returns the end time of last clip.
bool isTimecodeSyncEnabled() const noexcept
Returns true if syncing to MIDI timecode is enabled.
juce::CachedValue< bool > playInStopEnabled
Whether the audio engine should run when playback is stopped.
bool shouldPlay() const noexcept
Returns true if this Edit should be played back (or false if it was just opened for inspection).
AbletonLink & getAbletonLink() const noexcept
Returns the AbletonLink object.
AutomationRecordManager & getAutomationRecordManager() noexcept
Returns the AutomationRecordManager for the Edit.
bool isRendering() const noexcept
Returns true if the Edit is currently being rendered.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
TimePosition getPreviousTimeOfInterest(TimePosition beforeThisTime)
Finds the previous marker or start/end of a clip after a certain time.
int getNumCountInBeats() const
Returns the number of beats of the count in.
void updateMidiTimecodeDevices()
Updates the MIDI timecode/MMC devices.
juce::CachedValue< bool > recordingPunchInOut
Whether recoridng only happens within the in/out markers.
static TimeDuration getMaximumLength()
Returns the maximum length an Edit can be.
TimePosition getNextTimeOfInterest(TimePosition afterThisTime)
Finds the next marker or start/end of a clip after a certain time.
The Engine is the central class for all tracktion sessions.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
UIBehaviour & getUIBehaviour() const
Returns the UIBehaviour class.
ExternalControllerManager & getExternalControllerManager() const
Returns the ExternalControllerManager instance.
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
ActiveEdits & getActiveEdits() const noexcept
Returns the ActiveEdits instance.
static juce::File renderToFile(const juce::String &taskDescription, const Parameters ¶ms)
Renders an Edit to a file given by the Parameters.
TimePosition toTime(BeatPosition) const
Converts a number of beats a time.
BeatPosition toBeats(TimePosition) const
Converts a time to a number of beats.
TimeSigSetting * getTimeSig(int index) const
Returns the TimeSigSetting at a given index.
TempoSetting & getTempoAt(TimePosition) const
Returns the TempoSetting at the given position.
Controls the transport of an Edit's playback.
void playSectionAndReset(TimeRange rangeToPlay)
Plays a section of an Edit then stops playback, useful for previewing clips.
void setLoopIn(TimePosition)
Sets the loop in position.
void setFastForwardButtonDown(bool isDown)
Starts/stops a fast-forward operation.
TimePosition getPosition() const
Returns the current transport position.
void setLoopPoint2(TimePosition)
Sets a loop point 2 position.
void ensureContextAllocated(bool alwaysReallocate=false)
Ensures an active EditPlaybackContext has been created so this Edit can be played back.
bool isPlayContextActive() const
Returns true if this Edit is attached to the DeviceManager for playback.
void syncToEdit(Edit *editToSyncTo, bool syncToTargetLoopLength)
Syncs this Edit's playback to another Edit.
juce::ValueTree state
The state of this transport.
void setLoopRange(TimeRange)
Sets the loop points from a given range.
juce::Result applyRetrospectiveRecord(bool armedOnly)
Applys a retrospective record to any assigned input devices, creating clips for any historical input.
juce::Array< juce::File > getRetrospectiveRecordAsAudioFiles()
Perfoms a retrospective record operation and returns any new files.
void nudgeRight()
Moves the transport forwards slightly.
EditPlaybackContext * getCurrentPlaybackContext() const
Returns the active EditPlaybackContext if this Edit is attached to the DeviceManager for playback.
void freePlaybackContext()
Detaches the current EditPlaybackContext, removing it from the DeviceManager.
bool isRecordingStopping() const
Returns true if a recording is currently being stopped.
bool isRecording() const
Returns true if recording is in progress.
void setLoopPoint1(TimePosition)
Sets a loop point 1 position.
void play(bool justSendMMCIfEnabled)
Starts playback of an Edit.
void stopRecording(bool discardRecordings=false)
Stops recording without stopping playback.
bool isUserDragging() const noexcept
Returns true if a drag/scrub operation has been enabled.
void setUserDragging(bool)
Signifies a scrub-drag operation has started/stopped.
void forceOrphanFreezeAndProxyFilesPurge()
Triggers a cleanup of any unused freeze and proxy files.
void playFromStart(bool justSendMMCIfEnabled)
Sets the position to the startPosition and begins playback from there.
void nudgeLeft()
Moves the transport back slightly.
static juce::Array< TransportControl * > getAllActiveTransports(Engine &)
Returns all the active TransportControl[s] in the Engine.
~TransportControl() override
Destructor.
TimePosition getTimeWhenStarted() const
Returns the time when the transport was started.
bool isStopping() const
Returns true if the transport is currently being stopped.
bool isPositionUpdatingFromPlayhead() const
Returns true if the current position change was triggered from an update directly from the playhead (...
void editHasChanged()
Triggers a playback graph rebuild.
void stop(bool discardRecordings, bool clearDevices, bool canSendMMCStop=true)
Stops recording, creating clips of newyly recorded files/MIDI data.
void record(bool justSendMMCIfEnabled, bool allowRecordingIfNoInputsArmed=false)
Starts recording.
TimeRange getLoopRange() const noexcept
Returns the loop range.
void setPosition(TimePosition)
Sets a new transport position.
void setLoopOut(TimePosition)
Sets a loop out position.
bool isSafeRecording() const
Returns true if safe-recording is in progress.
TransportControl(Edit &, const juce::ValueTree &)
Constructs a TransportControl for an Edit.
void setRewindButtonDown(bool isDown)
Starts/stops a rewind operation.
bool isPlaying() const
Returns true if the transport is playing.
Edit & edit
The Edit this transport belongs to.
void setSnapType(TimecodeSnapType)
Sets a snap type to use.
void stopIfRecording()
Stops playback only if recording is currently in progress.
Engine & engine
The Engine this Edit belongs to.
juce::CachedValue< TimePosition > startPosition
The position to start playing from.
void triggerClearDevicesOnStop()
Triggers a graph rebuild when playback stops.
virtual void showWarningMessage(const juce::String &message)
Should display a temporary warning message.
An audio clip that uses an audio file as its source.
Converts a monotonically increasing reference range in to a timeline range.
#define TRANS(stringLiteral)
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
TimeRange getTimeRangeForSelectedItems(const SelectableList &selected)
Returns the time range covered by the given items.
juce::Array< Track * > getAllTracks(const Edit &edit)
Returns all the tracks in an Edit.
void markOut(TransportControl &tc)
Sets the mark out position to the current transport position.
void toStart(TransportControl &tc, const SelectableList &items)
Moves the transport to the start of the selected objects.
void freePlaybackContextIfNotRecording(TransportControl &tc)
Frees the playback context if no recording is in progress, useful for when an app is minimised.
void toEnd(TransportControl &tc, const SelectableList &items)
Moves the transport to the end of the selected objects.
bool isRecording(EditPlaybackContext &epc)
Returns true if any inputs are currently recording.
void markIn(TransportControl &tc)
Sets the mark in position to the current transport position.
void tabBack(TransportControl &tc)
Moves the transport back to the previous point of interest.
void tabForward(TransportControl &tc)
Moves the transport forwards to the next point of interest.
void scrub(TransportControl &tc, double units)
Scrubs back and forth in 'units', where a unit is about 1/50th of the width of the strip window.
TimeRange timeRangeFromSamples(juce::Range< int64_t > sampleRange, double sampleRate)
Creates a TimeRange from a range of samples.
constexpr int64_t toSamples(TimePosition, double sampleRate)
Converts a TimePosition to a number of samples.
RangeType< TimePosition > TimeRange
A RangeType based on real time (i.e.
constexpr TimeDuration toDuration(TimePosition)
Converts a TimePosition to a TimeDuration.
Represents a position in real-life time.
constexpr double inSeconds() const
Returns the TimePosition as a number of seconds.
ID for objects of type EditElement - e.g.
A list of Selectables, similar to a juce::Array but contains a cached list of the SelectableClasses f...
virtual void autoSaveNow()
Called periodically to indicate the Edit has changed in an audible way and should be auto-saved.
virtual void stopVideo()
Should stop video playback.
virtual void recordingAboutToStart(InputDeviceInstance &, EditItemID)
Called before recording start for a specific input instance.
virtual void recordingFinished(InputDeviceInstance &, EditItemID, const juce::ReferenceCountedArray< Clip > &)
Called when recording stops for a specific input instance.
virtual void recordingAboutToStop(InputDeviceInstance &, EditItemID)
Called before recording stops for a specific input instance.
virtual void recordingStopped(SyncPoint, bool)
Called when global recording stops.
virtual void setAllLevelMetersActive(bool)
If false, levels should be cleared.
virtual void startVideo()
Should start video playback.
TimePosition getLiveTransportPosition() const
Returns the transport position to show in the UI, taking in to account any latency.
~ReallocationInhibitor()
Enables playback graph regeneration.
ReallocationInhibitor(TransportControl &)
Stops an Edit creating a new playback graph.
Represents the state of an Edit's transport.
void stop(bool discardRecordings_, bool clearDevices_, bool canSendMMCStop_)
Stop playback/recording.
~TransportState() override
Destructor.
void play(bool justSendMMCIfEnabled_)
Start playback from the current transport position.
void record(bool justSendMMCIfEnabled_, bool allowRecordingIfNoInputsArmed_)
Start recording.
void setVideoPosition(TimePosition time, bool forceJump)
Updates the current video position, calling any listeners.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.