14#include "../../../3rd_party/nanorange/tracktion_nanorange.hpp"
16namespace tracktion::inline engine
22 return magic_enum::enum_cast<FollowAction> (s.toStdString());
31namespace follow_action_utils
36 AudioTrack::Ptr track;
41 size_t sceneIndex = 0, groupIndex = 0, indexInGroup = 0;
44 size_t getIndexInValidHandles()
const
46 if (
auto iter =
std::find (validSceneHandles.
begin(), validSceneHandles.
end(), launchHandle);
47 iter != validSceneHandles.
end())
56 return groups[groupIndex];
61 return groupIndex == 0 ? nullptr : &groups[groupIndex - 1];
66 return groupIndex == (groups.
size() - 1) ?
nullptr : &groups[groupIndex + 1];
71 if (groups.
size() == 1)
76 const auto index =
static_cast<size_t> (
random.nextInt ({ 0,
static_cast<int> (groups.
size()) }));
78 if (index != groupIndex)
79 return &groups[
static_cast<size_t> (index)];
86 auto sc = makeSafeRef (c);
87 auto audioTrack =
dynamic_cast<AudioTrack*
> (c.getTrack());
94 ctx->track = audioTrack;
95 ctx->launchHandle = c.getLaunchHandle();
99 for (
auto cs : audioTrack->getClipSlotList().getClipSlots())
101 if (
auto clip = cs->getClip())
103 auto lh = clip->getLaunchHandle();
107 ctx->validSceneHandles.emplace_back (std::move (lh));
115 if (ctx->validSceneHandles.empty())
119 ctx->sceneIndex =
static_cast<size_t> (c.getClipSlot()->getIndex());
121 for (
auto groupView : nano::split_view (allSceneHandles, {
nullptr }))
125 for (
auto lh : groupView)
127 if (ctx->launchHandle == lh)
129 ctx->groupIndex = ctx->groups.
size();
130 ctx->indexInGroup = group.
size();
139 assert (! contains_v (group,
nullptr));
140 ctx->groups.push_back (std::move (group));
143 assert (! ctx->groups.empty());
144 ctx->allSceneHandles = std::move (allSceneHandles);
150 FollowAction followAction)
154 assert (! ctx.getGroup().empty());
155 assert (! ctx.validSceneHandles.empty());
157 switch (followAction)
159 case globalReturnToArrangement: [[ fallthrough ]];
160 case trackAny: [[ fallthrough ]];
161 case trackOther: [[ fallthrough ]];
162 case currentGroupAny: [[ fallthrough ]];
163 case currentGroupOther: [[ fallthrough ]];
164 case previousGroupAny: [[ fallthrough ]];
165 case otherGroupFirst: [[ fallthrough ]];
166 case otherGroupLast: [[ fallthrough ]];
167 case nextGroupAny: [[ fallthrough ]];
169 assert (
false &&
"These actions are random can't return launch handles");
178 case globalPlayAgain:
179 return ctx.launchHandle;
183 if (
auto validIndex = ctx.getIndexInValidHandles(); validIndex > 0)
184 if (
auto prevHandle = ctx.validSceneHandles[validIndex - 1]; prevHandle)
191 if (
auto validIndex = ctx.getIndexInValidHandles(); validIndex < (ctx.validSceneHandles.size() - 1))
192 if (
auto nextHandle = ctx.validSceneHandles[validIndex + 1]; nextHandle)
199 return ctx.validSceneHandles.front();
203 return ctx.validSceneHandles.back();
205 case trackRoundRobin:
207 const auto validIndex = ctx.getIndexInValidHandles();
208 return ctx.validSceneHandles[(validIndex + 1) % ctx.validSceneHandles.size()];
210 case currentGroupPrevious:
212 if (ctx.indexInGroup > 0)
213 return ctx.getGroup()[(ctx.indexInGroup - 1)];
217 case currentGroupNext:
219 if (ctx.indexInGroup < (ctx.getGroup().size() - 1))
220 return ctx.getGroup()[(ctx.indexInGroup + 1)];
224 case currentGroupFirst:
226 return ctx.getGroup().front();
228 case currentGroupLast:
230 return ctx.getGroup().back();
232 case currentGroupRoundRobin:
234 auto &group = ctx.getGroup();
235 return group[(ctx.indexInGroup + 1) % group.
size()];
237 case previousGroupFirst:
239 if (
auto group = ctx.getPreviousGroup())
240 return group->
front();
244 case previousGroupLast:
246 if (
auto group = ctx.getPreviousGroup())
247 return group->
back();
253 if (
auto group = ctx.getNextGroup())
254 return group->
front();
260 if (
auto group = ctx.getNextGroup())
261 return group->
back();
272 FollowAction followAction)
276 switch (followAction)
278 case globalReturnToArrangement:
281 { ctx->track->playSlotClips =
true; };
285 return [ctx] (
auto b)
287 const auto index =
static_cast<size_t> (ctx->random.nextInt ({ 0,
static_cast<int> (ctx->validSceneHandles.size()) }));
288 ctx->validSceneHandles[index]->play (b);
293 if (ctx->validSceneHandles.size() > 1)
294 return [ctx] (
auto b)
298 const auto index =
static_cast<size_t> (ctx->random.nextInt ({ 0,
static_cast<int> (ctx->validSceneHandles.size()) }));
300 if (index == ctx->sceneIndex)
303 ctx->validSceneHandles[index]->play (b);
310 case currentGroupAny:
312 return [ctx, &group = ctx->getGroup()] (
auto b)
314 const auto index =
static_cast<size_t> (ctx->random.nextInt ({ 0,
static_cast<int> (group.size()) }));
315 group[index]->play (b);
318 case currentGroupOther:
320 if (ctx->getGroup().size() > 1)
321 return [ctx, &group = ctx->getGroup()] (
auto b)
325 const auto index =
static_cast<size_t> (ctx->random.nextInt ({ 0,
static_cast<int> (group.size()) }));
327 if (index == ctx->indexInGroup)
330 group[index]->play (b);
337 case previousGroupAny:
339 if (
auto group = ctx->getPreviousGroup())
340 return [ctx, group] (
auto b)
342 const auto index =
static_cast<size_t> (ctx->random.nextInt ({ 0,
static_cast<int> (group->size()) }));
343 (*group)[index]->play (b);
350 if (
auto group = ctx->getNextGroup())
351 return [ctx, group] (
auto b)
353 const auto index =
static_cast<size_t> (ctx->random.nextInt ({ 0,
static_cast<int> (group->size()) }));
354 (*group)[index]->play (b);
359 case otherGroupFirst:
361 if (ctx->groups.size() > 1)
362 return [ctx] (
auto b)
363 { ctx->getOtherGroup()->front()->play (b); };
369 if (ctx->groups.size() > 1)
370 return [ctx] (
auto b)
371 { ctx->getOtherGroup()->back()->play (b); };
377 if (ctx->groups.size() > 1)
378 return [ctx] (
auto b)
380 auto group = ctx->getOtherGroup();
381 const auto index =
static_cast<size_t> (ctx->random.nextInt ({ 0,
static_cast<int> (group->size()) }));
382 (*group)[index]->play (b);
387 case none: [[ fallthrough ]];
388 case globalStop: [[ fallthrough ]];
389 case globalPlayAgain: [[ fallthrough ]];
390 case trackPrevious: [[ fallthrough ]];
391 case trackNext: [[ fallthrough ]];
392 case trackFirst: [[ fallthrough ]];
393 case trackLast: [[ fallthrough ]];
394 case trackRoundRobin: [[ fallthrough ]];
395 case currentGroupPrevious: [[ fallthrough ]];
396 case currentGroupNext: [[ fallthrough ]];
397 case currentGroupFirst: [[ fallthrough ]];
398 case currentGroupLast: [[ fallthrough ]];
399 case currentGroupRoundRobin: [[ fallthrough ]];
400 case previousGroupFirst: [[ fallthrough ]];
401 case previousGroupLast: [[ fallthrough ]];
402 case nextGroupFirst: [[ fallthrough ]];
406 if (
auto lh = getLaunchHandle (*ctx, followAction))
407 return [lh] (
auto b) { lh->play (b); };
418 auto followActions = c.getFollowActions();
423 auto actions = followActions->getActions();
428 auto ctx = follow_action_utils::createClipContext (c);
431 if (actions.size() == 1)
432 return createFollowAction (ctx, followActions->getActions().front()->action.get());
434 struct FollowActionContainer
441 double maxProbability = 0.0;
443 for (
auto action : actions)
445 FollowActionContainer container;
446 container.probabilityRange = { maxProbability, maxProbability + action->weight };
447 container.action = createFollowAction (ctx, action->action);
448 followActionContainers.
push_back (std::move (container));
450 maxProbability = container.probabilityRange.getEnd();
453 return [followActionContainers = std::move (followActionContainers), ctx, maxProbability] (MonotonicBeat beat)
455 const auto randomValue = ctx->random.nextFloat() * maxProbability;
457 for (
auto& actionContainer : followActionContainers)
459 if (! actionContainer.probabilityRange.contains (randomValue))
462 if (actionContainer.action)
463 return actionContainer.action (beat);
490 return v.hasType (IDs::ACTION);
498 a->action.referTo (v, IDs::type, followActions.undoManager, FollowAction::currentGroupRoundRobin);
499 a->weight.referTo (v, IDs::weight, followActions.undoManager, 1.0);
500 a->state = std::move (v);
505 void deleteObject (
Action* a)
override
510 void newObjectAdded (
Action*)
override
512 followActions.sendChangeMessage();
515 void objectRemoved (
Action*)
override
517 followActions.sendSynchronousChangeMessage();
520 void objectOrderChanged()
override
522 followActions.sendChangeMessage();
531 : state (
std::move (v)), undoManager (um)
536FollowActions::~FollowActions()
540FollowActions::Action& FollowActions::addAction()
542 state.
appendChild ({ IDs::ACTION, {} }, undoManager);
543 return *getActions().back();
546void FollowActions::removeAction (Action& actionToRemove)
548 auto actionState = actionToRemove.action.getValueTree();
549 actionState.getParent().removeChild (actionState, undoManager);
554 return { list->begin(), list->end() };
void appendChild(const ValueTree &child, UndoManager *undoManager)
std::optional< FollowAction > followActionFromString(juce::String s)
Converts a string to a FollowAction if possible.
FollowAction
Determines the type of action to perform after a Clip has played for a set period.