tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_RackType.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14static constexpr int maxRackAudioChans = 64;
15
16static juce::String getDefaultInputName (int i)
17{
18 if (i == 0) return TRANS("Left input");
19 if (i == 1) return TRANS("Right input");
20
21 return TRANS("Input") + " " + juce::String (i + 1);
22}
23
24static juce::String getDefaultOutputName (int i)
25{
26 if (i == 0) return TRANS("Left output");
27 if (i == 1) return TRANS("Right output");
28
29 return TRANS("Output") + " " + juce::String (i + 1);
30}
31
32//==============================================================================
33RackConnection::RackConnection (const juce::ValueTree& v, juce::UndoManager* um)
34 : state (v)
35{
36 sourceID.referTo (state, IDs::src, um);
37 destID.referTo (state, IDs::dst, um);
38 sourcePin.referTo (state, IDs::srcPin, um);
39 destPin.referTo (state, IDs::dstPin, um);
40}
41
42//==============================================================================
43struct RackType::RackPluginList : public ValueTreeObjectList<RackType::PluginInfo>
44{
45 RackPluginList (RackType& t, const juce::ValueTree& parentTree)
46 : ValueTreeObjectList<PluginInfo> (parentTree), type (t)
47 {
49 callBlocking ([this] { this->rebuildObjects(); });
50 }
51
52 ~RackPluginList() override
53 {
54 freeObjects();
55 }
56
57 bool isSuitableType (const juce::ValueTree& v) const override
58 {
59 return v.hasType (IDs::PLUGININSTANCE);
60 }
61
62 PluginInfo* createNewObject (const juce::ValueTree& v) override
63 {
65 auto* i = new PluginInfo();
66 i->plugin = type.edit.getPluginCache().getOrCreatePluginFor (v.getChild(0));
67 i->state = v;
68 return i;
69 }
70
71 void deleteObject (PluginInfo* p) override
72 {
73 jassert (p != nullptr);
74 delete p;
75 }
76
77 void valueTreeChildAdded (juce::ValueTree& p, juce::ValueTree& tree) override
78 {
80
81 if (tree.hasType (IDs::PLUGIN) && p.hasType (IDs::PLUGININSTANCE))
82 for (auto info : objects)
83 if (info->plugin == nullptr && info->state == p)
84 info->plugin = type.edit.getPluginCache().getOrCreatePluginFor (tree);
85 }
86
87 void objectRemoved (PluginInfo*) override
88 {
89 removeBrokenConnections (type.state, type.getUndoManager());
90 }
91
92 void newObjectAdded (PluginInfo*) override {}
93 void objectOrderChanged() override {}
94 void valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier&) override {}
95
96 RackType& type;
97
99};
100
101struct RackType::ConnectionList : public ValueTreeObjectList<RackConnection>
102{
103 ConnectionList (RackType& t, const juce::ValueTree& parentTree)
104 : ValueTreeObjectList<RackConnection> (parentTree), type (t)
105 {
107 rebuildObjects();
108 }
109
110 ~ConnectionList() override
111 {
112 freeObjects();
113 }
114
115 bool isSuitableType (const juce::ValueTree& v) const override
116 {
117 return v.hasType (IDs::CONNECTION);
118 }
119
120 RackConnection* createNewObject (const juce::ValueTree& v) override
121 {
122 if (! type.edit.isLoading())
123 TRACKTION_ASSERT_MESSAGE_THREAD
124
125 return new RackConnection (v, type.getUndoManager());
126 }
127
128 void deleteObject (RackConnection* t) override
129 {
130 if (! type.edit.isLoading())
131 TRACKTION_ASSERT_MESSAGE_THREAD
132
133 jassert (t != nullptr);
134 delete t;
135 }
136
137 void newObjectAdded (RackConnection*) override {}
138 void objectRemoved (RackConnection*) override {}
139 void objectOrderChanged() override {}
140 void valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier&) override {}
141
142 RackType& type;
143
145};
146
147//==============================================================================
149{
151 : ValueTreeObjectList<WindowState> (t.state), type (t)
152 {
154 callBlocking ([this] { this->rebuildObjects(); });
155 }
156
157 ~WindowStateList() override
158 {
159 freeObjects();
160 }
161
162 bool isSuitableType (const juce::ValueTree& v) const override
163 {
164 return v.hasType (IDs::WINDOWSTATE);
165 }
166
167 WindowState* createNewObject (const juce::ValueTree& v) override
168 {
170 return new WindowState (type, v);
171 }
172
173 void deleteObject (WindowState* t) override
174 {
175 jassert (t != nullptr);
176 delete t;
177 }
178
179 void newObjectAdded (WindowState*) override {}
180 void objectRemoved (WindowState*) override {}
181 void objectOrderChanged() override {}
182 void valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier&) override {}
183
184 RackType& type;
185
187};
188
189//==============================================================================
190RackType::RackType (const juce::ValueTree& v, Edit& owner)
191 : MacroParameterElement (owner, v),
192 edit (owner), state (v),
193 rackID (EditItemID::readOrCreateNewID (edit, state))
194{
196
197 auto windowState = state.getChildWithName (IDs::WINDOWSTATE);
198
199 if (! windowState.isValid())
200 {
201 auto ws = createValueTree (IDs::WINDOWSTATE,
202 IDs::windowPos, state[IDs::windowPos]);
203
204 if (state.hasProperty (IDs::windowLocked))
205 ws.setProperty (IDs::windowLocked, state[IDs::windowLocked], nullptr);
206
207 state.addChild (ws, -1, nullptr);
208 }
209
210 windowStateList = std::make_unique<WindowStateList> (*this);
211
212 rackName.referTo (state, IDs::name, getUndoManager());
213
214 if (rackName.get().isEmpty())
215 rackName = TRANS("New Rack");
216
217 pluginList = std::make_unique<RackPluginList> (*this, state);
218 connectionList = std::make_unique<ConnectionList> (*this, state);
219
220 if (getOutputNames().isEmpty())
221 addDefaultOutputs();
222
223 if (getInputNames().isEmpty())
224 addDefaultInputs();
225
226 loadWindowPosition();
227 checkConnections();
228
229 modifierList = std::make_unique<ModifierList> (edit, state.getOrCreateChildWithName (IDs::MODIFIERS, getUndoManager()));
230
231 state.addListener (this);
232}
233
234RackType::~RackType()
235{
237 notifyListenersOfDeletion();
238 hideWindowForShutdown();
239 state.removeListener (this);
240 windowStateList.reset();
241}
242
243static juce::ValueTree createInOrOut (const juce::Identifier& type, const juce::String& name)
244{
245 return createValueTree (type,
246 IDs::name, name);
247}
248
249void RackType::removeAllInputsAndOutputs()
250{
251 for (int i = state.getNumChildren(); --i >= 0;)
252 {
253 auto v = state.getChild (i);
254
255 if (v.hasType (IDs::OUTPUT) || v.hasType (IDs::INPUT))
256 state.removeChild (i, getUndoManager());
257 }
258}
259
260void RackType::addDefaultInputs()
261{
262 state.addChild (createInOrOut (IDs::INPUT, "midi input"), -1, nullptr);
263 state.addChild (createInOrOut (IDs::INPUT, "input 1 (left)"), -1, nullptr);
264 state.addChild (createInOrOut (IDs::INPUT, "input 2 (right)"), -1, nullptr);
265}
266
267void RackType::addDefaultOutputs()
268{
269 state.addChild (createInOrOut (IDs::OUTPUT, "midi output"), -1, nullptr);
270 state.addChild (createInOrOut (IDs::OUTPUT, "output 1 (left)"), -1, nullptr);
271 state.addChild (createInOrOut (IDs::OUTPUT, "output 2 (right)"), -1, nullptr);
272}
273
274namespace
275{
276 bool hasAnyModifierAssignmentsRecursive (const juce::ValueTree& vt)
277 {
278 if (vt.hasType (IDs::MODIFIERASSIGNMENTS) && vt.getNumChildren() > 0)
279 return true;
280
281 for (auto v : vt)
282 if (hasAnyModifierAssignmentsRecursive (v))
283 return true;
284
285 return false;
286 }
287}
288
289juce::Result RackType::restoreStateFromValueTree (const juce::ValueTree& vt)
290{
291 // First check to see if the incoming state has any modifier assignments
292 if (hasAnyModifierAssignmentsRecursive (vt))
293 return juce::Result::fail (TRANS("Unable to apply preset due to Macro or Modifier connections, please create a new Rack from the preset"));
294
295 auto v = vt.createCopy();
296
297 if (v.hasType (IDs::PRESET))
298 v = v.getChildWithName (IDs::RACK);
299
300 if (! v.hasType (IDs::RACK))
301 return juce::Result::fail (TRANS("Invalid or corrupted preset"));
302
303 rackID.writeID (v, nullptr);
304
305 {
306 auto um = getUndoManager();
307 state.copyPropertiesFrom (v, um);
308
309 // Remove all children except the window state
310 for (int i = state.getNumChildren(); --i >= 0;)
311 {
312 auto c = state.getChild (i);
313
314 if (! (c.hasType (IDs::WINDOWSTATE) || c.hasType (IDs::MACROPARAMETERS) || c.hasType (IDs::MODIFIERS)))
315 state.removeChild (i, um);
316 }
317
318 // Add all children except window state, macros and modifiers
319 for (int i = v.getNumChildren(); --i >= 0;)
320 {
321 auto c = v.getChild (i);
322 v.removeChild (i, nullptr);
323
324 if (! (c.hasType (IDs::WINDOWSTATE) || c.hasType (IDs::MACROPARAMETERS) || c.hasType (IDs::MODIFIERS)))
325 state.addChild (c, 0, um);
326 }
327
328 checkConnections();
329
330 for (auto rf : activeRackInstances)
331 rf->changed();
332 }
333
334 propertiesChanged();
335 changed();
336
337 return juce::Result::ok();
338}
339
340juce::ValueTree RackType::createStateCopy (bool includeAutomation)
341{
342 saveWindowPosition();
343
344 for (auto p : getPlugins())
345 p->flushPluginStateToValueTree();
346
347 auto v = state.createCopy();
348
349 if (! includeAutomation)
350 AutomationCurve::removeAllAutomationCurvesRecursively (v);
351
352 return v;
353}
354
355juce::Array<RackType::WindowState*> RackType::getWindowStates() const
356{
357 return windowStateList->objects;
358}
359
360void RackType::loadWindowPosition()
361{
362 for (auto* ws : getWindowStates())
363 {
364 if (ws->state.hasProperty (IDs::windowPos))
365 ws->lastWindowBounds = juce::Rectangle<int>::fromString (ws->state[IDs::windowPos].toString());
366
367 if (ws->state.hasProperty (IDs::windowLocked))
368 ws->windowLocked = ws->state[IDs::windowLocked];
369 }
370}
371
372void RackType::saveWindowPosition()
373{
374 for (auto* ws : getWindowStates())
375 {
376 auto windowState = ws->lastWindowBounds.toString();
377 ws->state.setProperty (IDs::windowPos, windowState.isEmpty() ? juce::var() : juce::var (windowState), nullptr);
378 ws->state.setProperty (IDs::windowLocked, ws->windowLocked, nullptr);
379 }
380}
381
382RackType::Ptr RackType::createTypeToWrapPlugins (const Plugin::Array& plugins, Edit& sourceEdit)
383{
384 auto rack = sourceEdit.getRackList().addNewRack();
385
386 if (plugins.size() == 1)
387 rack->rackName = plugins.getFirst()->getName() + " " + TRANS("Wrapper");
388 else if (plugins.getFirst()->getOwnerTrack() == nullptr)
389 rack->rackName = TRANS("Master Wrapper");
390 else
391 rack->rackName = plugins.getFirst()->getOwnerTrack()->getName() + " " + TRANS("Wrapper");
392
393 rack->removeInput (2);
394 rack->removeInput (1);
395 rack->removeOutput (2);
396 rack->removeOutput (1);
397
398 for (int i = 0; i < plugins.size(); ++i)
399 if (auto f = plugins[i])
400 rack->addPlugin (f, juce::Point<float> (1.0f / (plugins.size() + 1) * (i + 1), 0.5f), false);
401
402 juce::StringArray ins, outs;
403 plugins.getFirst()->getChannelNames (&ins, nullptr);
404 plugins.getLast()->getChannelNames (nullptr, &outs);
405
406 // connect the left side to the first plugin
407 for (int i = 0; i < std::min (maxRackAudioChans, ins.size()); ++i)
408 {
409 auto name = ins[i];
410
411 if (name.isEmpty() || name.equalsIgnoreCase (TRANS("Unnamed")))
412 name = getDefaultInputName (i);
413
414 rack->addInput (-1, name);
415 rack->addConnection ({}, i + 1, plugins.getFirst()->itemID, i + 1);
416 }
417
418 // connect the right side to the last plugin
419 for (int i = 0; i < std::min (maxRackAudioChans, outs.size()); ++i)
420 {
421 auto name = outs[i];
422
423 if (name.isEmpty() || name.equalsIgnoreCase (TRANS("Unnamed")))
424 name = getDefaultOutputName (i);
425
426 rack->addOutput (-1, name);
427 rack->addConnection (plugins.getLast()->itemID, i + 1, {}, i + 1);
428 }
429
430 // midi connections for the first and last plugin
431 rack->addConnection ({}, 0, plugins.getFirst()->itemID, 0);
432 rack->addConnection (plugins.getLast()->itemID, 0, {}, 0);
433
434 for (int i = 0; i < plugins.size() - 1; ++i)
435 {
436 auto fsrc = plugins[i];
437 auto fdst = plugins[i + 1];
438
439 juce::StringArray dstIns, srcOuts;
440 fsrc->getChannelNames (nullptr, &srcOuts);
441 fdst->getChannelNames (&dstIns, nullptr);
442
443 rack->addConnection (fsrc->itemID, 0, fdst->itemID, 0);
444
445 for (int j = 0; j < std::min (srcOuts.size(), dstIns.size()); ++j)
446 rack->addConnection (fsrc->itemID, j + 1, fdst->itemID, j + 1);
447 }
448
449 return rack;
450}
451
452juce::StringArray RackType::getInputNames() const
453{
455
456 for (const auto& v : state)
457 if (v.hasType (IDs::INPUT))
458 s.add (v.getProperty (IDs::name));
459
460 return s;
461}
462
463juce::StringArray RackType::getOutputNames() const
464{
466
467 for (const auto& v : state)
468 if (v.hasType (IDs::OUTPUT))
469 s.add (v.getProperty (IDs::name));
470
471 return s;
472}
473
474juce::Array<Plugin*> RackType::getPlugins() const
475{
477
478 for (auto i : pluginList->objects)
479 if (i->plugin != nullptr)
480 list.add (i->plugin.get());
481
482 return list;
483}
484
485bool RackType::isPluginAllowed (const Plugin::Ptr& p)
486{
487 return p != nullptr && p->canBeAddedToRack();
488}
489
490bool RackType::addPlugin (const Plugin::Ptr& p, juce::Point<float> pos, bool canAutoConnect)
491{
492 if (! isPluginAllowed (p))
493 return false;
494
495 if (! getPlugins().contains (p.get()))
496 {
498
499 bool autoConnect = canAutoConnect && pluginList->objects.isEmpty();
500
501 p->removeFromParent();
502
503 auto v = createValueTree (IDs::PLUGININSTANCE,
504 IDs::x, juce::jlimit (0.0f, 1.0f, pos.x),
505 IDs::y, juce::jlimit (0.0f, 1.0f, pos.y));
506 v.addChild (p->state, -1, getUndoManager());
507
508 state.addChild (v, -1, getUndoManager());
509
510 if (autoConnect)
511 {
512 juce::StringArray ins, outs;
513 p->getChannelNames (&ins, &outs);
514
515 while (outs.size() > getOutputNames().size() - 1)
516 if (addOutput (getOutputNames().size(), TRANS("Output") + " " + juce::String (getOutputNames().size())) == -1)
517 break;
518
519 for (int i = 0; i < ins.size(); ++i) addConnection ({}, i + 1, p->itemID, i + 1);
520 for (int i = 0; i < outs.size(); ++i) addConnection (p->itemID, i + 1, {}, i + 1);
521
522 // midi connections
523 addConnection ({}, 0, p->itemID, 0);
524 addConnection (p->itemID, 0, {}, 0);
525 }
526
527 return true;
528 }
529
530 return false;
531}
532
533juce::Point<float> RackType::getPluginPosition (const Plugin::Ptr& p) const
534{
535 for (auto info : pluginList->objects)
536 if (info->plugin == p)
537 return { info->state[IDs::x],
538 info->state[IDs::y] };
539
540 return {};
541}
542
543juce::Point<float> RackType::getPluginPosition (int index) const
544{
545 if (auto info = pluginList->objects[index])
546 return { info->state[IDs::x],
547 info->state[IDs::y] };
548
549 return {};
550}
551
552void RackType::setPluginPosition (int index, juce::Point<float> pos)
553{
554 if (auto info = pluginList->objects[index])
555 {
556 info->state.setProperty (IDs::x, juce::jlimit (0.0f, 1.0f, pos.x), getUndoManager());
557 info->state.setProperty (IDs::y, juce::jlimit (0.0f, 1.0f, pos.y), getUndoManager());
558 }
559}
560
561//==============================================================================
562juce::Array<EditItemID> RackType::getPluginsWhichTakeInputFrom (EditItemID sourceId) const
563{
565
566 if (sourceId.isValid())
567 for (auto rc : connectionList->objects)
568 if (rc->sourceID == sourceId && rc->destID.get().isValid())
569 results.addIfNotAlreadyThere (rc->destID);
570
571 return results;
572}
573
574bool RackType::arePluginsConnectedIndirectly (EditItemID src, EditItemID dest, int depth) const
575{
576 if (depth < 100) // to avoid loops
577 {
578 auto dests = getPluginsWhichTakeInputFrom (src);
579
580 for (auto& d : dests)
581 if (d == dest)
582 return true;
583
584 for (auto& d : dests)
585 if (arePluginsConnectedIndirectly (d, dest, depth + 1))
586 return true;
587 }
588
589 return false;
590}
591
592bool RackType::isConnectionLegal (EditItemID source, int sourcePin,
593 EditItemID dest, int destPin) const
594{
595 if (! source.isValid())
596 {
597 if (sourcePin >= getInputNames().size())
598 return false;
599 }
600 else
601 {
602 juce::StringArray ins, outs;
603
604 if (auto p = edit.getPluginCache().getPluginFor (source))
605 p->getChannelNames (&ins, &outs);
606
607 if (sourcePin > outs.size())
608 return false;
609 }
610
611 if (! dest.isValid())
612 {
613 if (destPin >= getOutputNames().size())
614 return false;
615 }
616 else
617 {
618 juce::StringArray ins, outs;
619
620 if (auto p = edit.getPluginCache().getPluginFor (dest))
621 p->getChannelNames (&ins, &outs);
622 else if (auto m = findModifierForID (getModifierList(), dest))
623 ins = m->getAudioInputNames();
624
625 if (destPin > ins.size())
626 return false;
627 }
628
629 if (! (source.isValid() || dest.isValid()))
630 return true;
631
632 if (source == dest)
633 return false;
634
635 return ! arePluginsConnectedIndirectly (dest, source);
636}
637
638bool RackType::addConnection (EditItemID srcId, int sourcePin,
639 EditItemID dstId, int destPin)
640{
641 if (! isConnectionLegal (srcId, sourcePin, dstId, destPin))
642 return false;
643
644 for (auto rc : connectionList->objects)
645 if (rc->destID == dstId && rc->destPin == destPin
646 && rc->sourceID == srcId && rc->sourcePin == sourcePin)
647 return false;
648
649 auto v = createValueTree (IDs::CONNECTION,
650 IDs::src, srcId,
651 IDs::dst, dstId,
652 IDs::srcPin, sourcePin,
653 IDs::dstPin, destPin);
654
655 state.addChild (v, -1, getUndoManager());
656 return true;
657}
658
659bool RackType::removeConnection (EditItemID srcId, int sourcePin,
660 EditItemID dstId, int destPin)
661{
662 TRACKTION_ASSERT_MESSAGE_THREAD
663
664 for (int i = connectionList->objects.size(); --i >= 0;)
665 {
666 if (auto rc = connectionList->objects[i])
667 {
668 if (rc->destID == dstId && rc->sourceID == srcId
669 && rc->destPin == destPin && rc->sourcePin == sourcePin)
670 {
671 state.removeChild (rc->state, getUndoManager());
672 return true;
673 }
674 }
675 }
676
677 return false;
678}
679
680void RackType::checkConnections()
681{
682 if (! edit.isLoading())
683 TRACKTION_ASSERT_MESSAGE_THREAD
684
685 for (int i = connectionList->objects.size(); --i >= 0;)
686 {
687 if (auto rc = connectionList->objects[i])
688 {
689 // Check for connections going to no longer available rack IO pins
690 if (((rc->sourcePin < 0 || rc->sourcePin >= getInputNames().size()) && rc->sourceID->isInvalid())
691 || ((rc->destPin < 0 || rc->destPin >= getOutputNames().size()) && rc->destID->isInvalid()))
692 {
693 state.removeChild (rc->state, getUndoManager());
694 continue;
695 }
696
697 // Avoid stripping connections when the Edit hasn't loaded the plugins as the channels will be 0
698 if (! edit.shouldLoadPlugins())
699 continue;
700
701 // Check for connections going to no longer available plugin IO pins
702 if (auto ep = dynamic_cast<ExternalPlugin*> (getPluginForID (rc->sourceID)))
703 {
704 if (ep->getAudioPluginInstance() != nullptr)
705 {
706 if (rc->sourcePin < 0 || rc->sourcePin > ep->getNumOutputs())
707 {
708 state.removeChild (rc->state, getUndoManager());
709 continue;
710 }
711 }
712 }
713
714 if (auto ep = dynamic_cast<ExternalPlugin*> (getPluginForID (rc->destID)))
715 {
716 if (ep->getAudioPluginInstance() != nullptr)
717 {
718 if (rc->destPin < 0 || rc->destPin > ep->getNumInputs())
719 {
720 state.removeChild (rc->state, getUndoManager());
721 continue;
722 }
723 }
724 }
725 }
726 }
727}
728
729juce::Array<const RackConnection*> RackType::getConnections() const noexcept
730{
732
733 for (auto rc : connectionList->objects)
734 list.add (rc);
735
736 return list;
737}
738
739//==============================================================================
740static bool findModifierWithID (juce::ValueTree& modifiers, EditItemID itemID)
741{
742 for (auto m : modifiers)
743 if (EditItemID::fromID (m) == itemID)
744 return true;
745
746 return false;
747}
748
749static bool findPluginOrModifierWithID (juce::ValueTree& rack, EditItemID itemID)
750{
751 for (int i = rack.getNumChildren(); --i >= 0;)
752 {
753 auto c = rack.getChild (i);
754
755 if (c.hasType (IDs::PLUGININSTANCE))
756 if (EditItemID::fromID (c.getChildWithName (IDs::PLUGIN)) == itemID)
757 return true;
758
759 if (c.hasType (IDs::MODIFIERS))
760 if (findModifierWithID (c, itemID))
761 return true;
762 }
763
764 return false;
765}
766
767static int countNumConnections (juce::ValueTree& rack, const juce::Identifier& type)
768{
769 int count = 0;
770
771 for (int i = rack.getNumChildren(); --i >= 0;)
772 if (rack.getChild (i).hasType (type))
773 ++count;
774
775 return count;
776}
777
778static bool connectionIsValid (juce::ValueTree& rack, EditItemID srcID,
779 int srcPin, EditItemID dstID, int dstPin)
780{
781 if (srcID.isInvalid())
782 {
783 if (srcPin < 0 || srcPin >= countNumConnections (rack, IDs::INPUT))
784 return false;
785 }
786 else if (! findPluginOrModifierWithID (rack, srcID))
787 {
788 return false;
789 }
790
791 if (dstID.isInvalid())
792 {
793 if (dstPin < 0 || dstPin >= countNumConnections (rack, IDs::OUTPUT))
794 return false;
795 }
796 else if (! findPluginOrModifierWithID (rack, dstID))
797 {
798 return false;
799 }
800
801 return true;
802}
803
804void RackType::removeBrokenConnections (juce::ValueTree& rack, juce::UndoManager* um)
805{
806 for (int i = rack.getNumChildren(); --i >= 0;)
807 {
808 auto c = rack.getChild (i);
809
810 if (c.hasType (IDs::CONNECTION)
811 && ! connectionIsValid (rack,
812 EditItemID::fromProperty (c, IDs::src), c[IDs::srcPin],
813 EditItemID::fromProperty (c, IDs::dst), c[IDs::dstPin]))
814 rack.removeChild (i, um);
815 }
816}
817
818void RackType::createInstanceForSideChain (Track& at, const juce::BigInteger& channelMask,
819 EditItemID pluginID, int pinIndex)
820{
821 const bool connectLeft = channelMask[0];
822 const bool connectRight = channelMask[1];
823 auto& pl = at.pluginList;
824 RackInstance* rack = nullptr;
825
826 for (auto p : pl)
827 if (auto rf = dynamic_cast<RackInstance*> (p))
828 if (rf->type.get() == this)
829 rack = rf;
830
831 if (rack == nullptr)
832 {
833 if (auto p = edit.getPluginCache().getOrCreatePluginFor (RackInstance::create (*this)))
834 {
835 pl.insertPlugin (p, 0, nullptr);
836 rack = dynamic_cast<RackInstance*> (p.get());
837 }
838 }
839
840 if (rack != nullptr)
841 {
842 auto inputs = getInputNames();
843 const juce::String leftName ("sidechain input (left)");
844 const juce::String rightName ("sidechain input (right)");
845 auto noneName = rack->getNoPinName();
846
847 if (connectLeft)
848 {
849 int srcPinIndex = inputs.indexOf (leftName);
850
851 if (srcPinIndex == -1)
852 srcPinIndex = addInput (-1, leftName);
853
854 rack->setInputName (RackInstance::left, leftName);
855 rack->setOutputName (RackInstance::left, noneName);
856
857 if (! connectRight)
858 {
859 rack->setInputName (RackInstance::right, noneName);
860 rack->setOutputName (RackInstance::right, noneName);
861 }
862
863 if (srcPinIndex >= 0)
864 addConnection ({}, srcPinIndex, pluginID, pinIndex);
865 }
866
867 if (connectRight)
868 {
869 int srcPinIndex = inputs.indexOf (rightName);
870
871 if (srcPinIndex == -1)
872 srcPinIndex = addInput (-1, rightName);
873
874 rack->setInputName (RackInstance::right, rightName);
875 rack->setOutputName (RackInstance::right, noneName);
876
877 if (! connectLeft)
878 {
879 rack->setInputName (RackInstance::left, noneName);
880 rack->setOutputName (RackInstance::left, noneName);
881 }
882
883 if (srcPinIndex != -1)
884 addConnection ({}, srcPinIndex, pluginID,
885 connectLeft ? pinIndex + 1 : pinIndex);
886 }
887
888 rack->wetGain->setParameter (0.0f, juce::dontSendNotification);
889 rack->dryGain->setParameter (1.0f, juce::dontSendNotification);
890
891 SelectionManager::refreshAllPropertyPanelsShowing (*rack);
892 }
893 else
894 {
895 edit.engine.getUIBehaviour().showWarningAlert (TRANS("Unable to create side-chain"),
896 TRANS("Unable to create rack on source track"));
897 }
898}
899
900//==============================================================================
902{
903 return TRANS("Plugin Rack");
904}
905
906//==============================================================================
907static int findIndexOfNthInstanceOf (juce::ValueTree& parent, const juce::Identifier& type, int index)
908{
909 int count = 0;
910
911 for (int i = 0; i < parent.getNumChildren(); ++i)
912 {
913 auto v = parent.getChild (i);
914
915 if (v.hasType (type))
916 if (count++ == index)
917 return i;
918 }
919
920 return -1;
921}
922
923int RackType::addInput (int index, const juce::String& name)
924{
925 int numNames = getInputNames().size();
926
927 if (numNames <= maxRackAudioChans)
928 {
929 if (index >= 0)
930 {
931 for (auto conn : connectionList->objects)
932 if (conn->sourceID->isInvalid() && conn->sourcePin >= index)
933 conn->sourcePin = conn->sourcePin + 1;
934 }
935
936 state.addChild (createInOrOut (IDs::INPUT, name),
937 index < 0 ? findIndexOfNthInstanceOf (state, IDs::INPUT, numNames - 1) + 1
938 : findIndexOfNthInstanceOf (state, IDs::INPUT, index),
939 getUndoManager());
940
941 if (index < 0)
942 index = numNames;
943
944 jassert (getInputNames()[index] == name);
945 return index;
946 }
947
948 return -1;
949}
950
951int RackType::addOutput (int index, const juce::String& name)
952{
953 int numNames = getOutputNames().size();
954
955 if (numNames <= maxRackAudioChans)
956 {
957 if (index >= 0)
958 {
959 for (auto conn : connectionList->objects)
960 if (conn->destID->isInvalid() && conn->destPin >= index)
961 conn->destPin = conn->destPin + 1;
962 }
963
964 state.addChild (createInOrOut (IDs::OUTPUT, name),
965 index < 0 ? findIndexOfNthInstanceOf (state, IDs::OUTPUT, numNames - 1) + 1
966 : findIndexOfNthInstanceOf (state, IDs::OUTPUT, index),
967 getUndoManager());
968
969 if (index < 0)
970 index = numNames;
971
972 jassert (getOutputNames()[index] == name);
973 return index;
974 }
975
976 return -1;
977}
978
979void RackType::removeInput (int index)
980{
981 TRACKTION_ASSERT_MESSAGE_THREAD
982 int toRemove = findIndexOfNthInstanceOf (state, IDs::INPUT, index);
983
984 if (toRemove >= 0)
985 {
986 for (auto conn : connectionList->objects)
987 if (conn->sourceID->isInvalid() && conn->sourcePin > index)
988 conn->sourcePin = conn->sourcePin - 1;
989
990 state.removeChild (toRemove, getUndoManager());
991 checkConnections();
992 }
993}
994
995void RackType::removeOutput (int index)
996{
997 TRACKTION_ASSERT_MESSAGE_THREAD
998 int toRemove = findIndexOfNthInstanceOf (state, IDs::OUTPUT, index);
999
1000 if (toRemove >= 0)
1001 {
1002 for (auto conn : connectionList->objects)
1003 if (conn->destID->isInvalid() && conn->destPin > index)
1004 conn->destPin = conn->destPin - 1;
1005
1006 state.removeChild (toRemove, getUndoManager());
1007 checkConnections();
1008 }
1009}
1010
1011static juce::String checkChannelName (const juce::String& name)
1012{
1013 return name.trim().isEmpty() ? TRANS("Unnamed") : name;
1014}
1015
1016void RackType::renameInput (int index, const juce::String& name)
1017{
1018 int toRename = findIndexOfNthInstanceOf (state, IDs::INPUT, index);
1019
1020 if (toRename >= 0)
1021 state.getChild (toRename).setProperty (IDs::name, checkChannelName (name), getUndoManager());
1022}
1023
1024void RackType::renameOutput (int index, const juce::String& name)
1025{
1026 int toRename = findIndexOfNthInstanceOf (state, IDs::OUTPUT, index);
1027
1028 if (toRename >= 0)
1029 state.getChild (toRename).setProperty (IDs::name, checkChannelName (name), getUndoManager());
1030}
1031
1032//==============================================================================
1033juce::UndoManager* RackType::getUndoManager() const
1034{
1035 return &edit.getUndoManager();
1036}
1037
1038void RackType::countInstancesInEdit()
1039{
1041 TRACKTION_ASSERT_MESSAGE_THREAD
1042
1043 numberOfInstancesInEdit = 0;
1044
1045 for (auto p : getAllPlugins (edit, false))
1046 if (auto rf = dynamic_cast<RackInstance*> (p))
1047 if (rf->type.get() == this)
1048 ++numberOfInstancesInEdit;
1049}
1050
1051//==============================================================================
1052void RackType::registerInstance (RackInstance* instance, const PluginInitialisationInfo&)
1053{
1055 activeRackInstances.addIfNotAlreadyThere (instance);
1056 numActiveInstances.store (activeRackInstances.size());
1057
1058 countInstancesInEdit();
1059}
1060
1061void RackType::deregisterInstance (RackInstance* instance)
1062{
1063 activeRackInstances.removeAllInstancesOf (instance);
1064 numActiveInstances.store (activeRackInstances.size());
1065
1066 countInstancesInEdit();
1067}
1068
1069void RackType::updateAutomatableParamPositions (TimePosition time)
1070{
1071 for (auto f : getPlugins())
1072 f->updateAutomatableParamPosition (time);
1073
1074 for (auto m : getModifierList().getModifiers())
1075 m->updateAutomatableParamPosition (time);
1076}
1077
1078//==============================================================================
1079RackType::Ptr RackType::findRackTypeContaining (const Plugin& plugin)
1080{
1081 for (auto p : getAllPlugins (plugin.edit, false))
1082 if (auto rack = dynamic_cast<RackInstance*> (p))
1083 if (rack->type != nullptr)
1084 if (rack->type->getPluginForID (plugin.itemID))
1085 return rack->type;
1086
1087 return {};
1088}
1089
1090//==============================================================================
1091static juce::StringArray stripAndPrependChannels (juce::StringArray names)
1092{
1093 names.remove (0);
1094 int i = 0;
1095
1096 for (auto&& n : names)
1097 n = juce::String (++i) + ". " + n;
1098
1099 return names;
1100}
1101
1102juce::StringArray RackType::getAudioInputNamesWithChannels() const
1103{
1104 return stripAndPrependChannels (getInputNames());
1105}
1106
1107juce::StringArray RackType::getAudioOutputNamesWithChannels() const
1108{
1109 return stripAndPrependChannels (getOutputNames());
1110}
1111
1112//==============================================================================
1113Plugin* RackType::getPluginForID (EditItemID pluginID)
1114{
1115 if (pluginID.isInvalid())
1116 return {};
1117
1118 for (auto p : pluginList->objects)
1119 if (p->plugin != nullptr && p->plugin->itemID == pluginID)
1120 return p->plugin.get();
1121
1122 return {};
1123}
1124
1125void RackType::hideWindowForShutdown()
1126{
1127 for (auto af : getPlugins())
1128 if (auto ep = dynamic_cast<ExternalPlugin*> (af))
1129 ep->hideWindowForShutdown();
1130
1131 for (auto ws : getWindowStates())
1132 ws->hideWindowForShutdown();
1133}
1134
1135bool RackType::pasteClipboard()
1136{
1137 auto sm = edit.engine.getUIBehaviour().getSelectionManagerForRack (*this);
1138
1139 if (sm == nullptr)
1140 return false;
1141
1142 SelectableList pastedItems;
1143
1144 if (auto plugins = Clipboard::getInstance()->getContentWithType<Clipboard::Plugins>())
1145 {
1146 for (auto& pluginState : plugins->plugins)
1147 {
1148 auto stateCopy = pluginState.createCopy();
1149 EditItemID::remapIDs (stateCopy, nullptr, edit);
1150
1151 if (auto newPlugin = edit.getPluginCache().getOrCreatePluginFor (stateCopy))
1152 {
1153 auto selectedPlugin = sm->getFirstItemOfType<Plugin>();
1154 RackType::Ptr selectedRack (sm->getFirstItemOfType<RackType>());
1155
1156 if (selectedPlugin != nullptr || selectedRack != nullptr)
1157 {
1158 if (selectedRack == nullptr)
1159 selectedRack = edit.getRackList().findRackContaining (*selectedPlugin);
1160
1161 if (selectedRack != nullptr)
1162 {
1163 juce::Point<float> pos (0.1f, 0.1f);
1164
1165 if (selectedPlugin != nullptr && selectedRack.get() == this)
1166 pos = getPluginPosition (*selectedPlugin).translated (0.1f, 0.1f);
1167
1168 for (auto af : getPlugins())
1169 if (pos == getPluginPosition (*af))
1170 pos = pos.translated (0.1f, 0.1f);
1171
1172 selectedRack->addPlugin (newPlugin, pos, false);
1173 pastedItems.add (newPlugin.get());
1174 }
1175 }
1176 }
1177 }
1178 }
1179
1180 sm->select (pastedItems);
1181
1182 return pastedItems.isNotEmpty();
1183}
1184
1185//==============================================================================
1186ModifierList& RackType::getModifierList() const noexcept
1187{
1188 return *modifierList;
1189}
1190
1191//==============================================================================
1193{
1194 ValueTreeList (RackTypeList& l, const juce::ValueTree& parentTree)
1195 : ValueTreeObjectList<RackType> (parentTree), list (l)
1196 {
1197 // NB: rebuildObjects() is called after construction so that the edit has a valid
1198 // list while they're being created
1199 }
1200
1201 ~ValueTreeList() override
1202 {
1203 freeObjects();
1204 }
1205
1206 bool isSuitableType (const juce::ValueTree& v) const override
1207 {
1208 return v.hasType (IDs::RACK);
1209 }
1210
1211 RackType* createNewObject (const juce::ValueTree& v) override
1212 {
1213 auto t = new RackType (v, list.edit);
1214 t->incReferenceCount();
1215 return t;
1216 }
1217
1218 void deleteObject (RackType* t) override
1219 {
1220 jassert (t != nullptr);
1221 t->decReferenceCount();
1222 }
1223
1224 void newObjectAdded (RackType*) override { sendChange(); }
1225 void objectRemoved (RackType*) override { sendChange(); }
1226 void objectOrderChanged() override { sendChange(); }
1227 void valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier&) override { sendChange(); }
1228
1229 void sendChange()
1230 {
1231 // XXX
1232 }
1233
1234 RackTypeList& list;
1235
1237};
1238
1239//==============================================================================
1240RackTypeList::RackTypeList (Edit& ed) : edit (ed)
1241{
1242}
1243
1244void RackTypeList::initialise (const juce::ValueTree& v)
1245{
1247
1248 state = v;
1249 jassert (state.hasType (IDs::RACKS));
1250
1251 list = std::make_unique<ValueTreeList> (*this, v);
1252 list->rebuildObjects();
1253}
1254
1255RackTypeList::~RackTypeList()
1256{
1257 for (auto t : list->objects)
1258 t->hideWindowForShutdown();
1259
1260 list = nullptr;
1261}
1262
1263const juce::Array<RackType*>& RackTypeList::getTypes() const noexcept
1264{
1265 return list->objects;
1266}
1267
1268int RackTypeList::size() const
1269{
1270 return list->objects.size();
1271}
1272
1273RackType::Ptr RackTypeList::getRackType (int index) const
1274{
1275 return list->objects[index];
1276}
1277
1278RackType::Ptr RackTypeList::getRackTypeForID (EditItemID rackID) const
1279{
1280 for (auto r : list->objects)
1281 if (r->rackID == rackID)
1282 return *r;
1283
1284 return {};
1285}
1286
1287RackType::Ptr RackTypeList::findRackContaining (Plugin& p) const
1288{
1289 for (auto r : list->objects)
1290 if (r->getPlugins().contains (&p))
1291 return *r;
1292
1293 return {};
1294}
1295
1296void RackTypeList::removeRackType (const RackType::Ptr& type)
1297{
1298 if (list->objects.contains (type.get()))
1299 {
1300 auto allTracks = getAllTracks (edit);
1301
1302 for (auto f : getAllPlugins (edit, false))
1303 if (auto rf = dynamic_cast<RackInstance*> (f))
1304 if (rf->type == type)
1305 rf->deleteFromParent();
1306
1307 // Remove any Macros or Modifiers that might be assigned
1308 if (auto mpl = type->getMacroParameterList())
1309 mpl->hideMacroParametersFromTracks();
1310
1311 for (auto macro : type->getMacroParameters())
1312 for (auto param : getAllParametersBeingModifiedBy (edit, *macro))
1313 param->removeModifier (*macro);
1314
1315 for (auto modifier : type->getModifierList().getModifiers())
1316 {
1317 for (auto t : allTracks)
1318 t->hideAutomatableParametersForSource (modifier->itemID);
1319
1320 for (auto param : getAllParametersBeingModifiedBy (edit, *modifier))
1321 param->removeModifier (*modifier);
1322 }
1323
1324 type->hideWindowForShutdown();
1325 state.removeChild (type->state, &edit.getUndoManager());
1326 }
1327}
1328
1329RackType::Ptr RackTypeList::addNewRack()
1330{
1331 auto newID = edit.createNewItemID();
1332
1333 juce::ValueTree v (IDs::RACK);
1334 newID.writeID (v, nullptr);
1335 state.addChild (v, -1, &edit.getUndoManager());
1336
1337 auto type = getRackTypeForID (newID);
1338 jassert (type != nullptr);
1339
1340 if (edit.engine.getEngineBehaviour().arePluginsRemappedWhenTempoChanges())
1341 type->getMacroParameterListForWriting().remapOnTempoChange = true;
1342
1343 return type;
1344}
1345
1346RackType::Ptr RackTypeList::addRackTypeFrom (const juce::ValueTree& rackType)
1347{
1348 if (! rackType.hasType (IDs::RACK))
1349 return {};
1350
1351 auto typeID = EditItemID::fromID (rackType);
1352
1353 if (typeID.isInvalid())
1354 return {};
1355
1356 auto type = getRackTypeForID (typeID);
1357
1358 if (type == nullptr)
1359 {
1360 if (rackType.isValid())
1361 {
1362 state.addChild (rackType.createCopy(), -1, &edit.getUndoManager());
1363
1364 type = getRackTypeForID (typeID);
1365 jassert (type != nullptr);
1366
1367 if (edit.engine.getEngineBehaviour().arePluginsRemappedWhenTempoChanges())
1368 type->getMacroParameterListForWriting().remapOnTempoChange = true;
1369 }
1370 }
1371
1372 return type;
1373}
1374
1375void RackTypeList::importRackFiles (const juce::Array<juce::File>& files)
1376{
1377 int oldNumRacks = size();
1378
1379 for (auto& f : files)
1380 if (auto xml = juce::parseXML (f))
1381 addRackTypeFrom (juce::ValueTree::fromXml (*xml));
1382
1383 if (oldNumRacks < size())
1384 edit.engine.getUIBehaviour().showWarningMessage (TRANS("Rack types added!"));
1385}
1386
1387//==============================================================================
1388void RackType::triggerUpdate()
1389{
1391
1392 if (edit.isLoading())
1393 return;
1394
1395 countInstancesInEdit();
1396
1397 edit.restartPlayback();
1398}
1399
1400void RackType::updateRenderContext()
1401{
1402 if (edit.isLoading())
1403 TRACKTION_ASSERT_MESSAGE_THREAD
1404}
1405
1406//==============================================================================
1407void RackType::valueTreeChildAdded (juce::ValueTree&, juce::ValueTree&)
1408{
1409 triggerUpdate();
1410}
1411
1412void RackType::valueTreeChildRemoved (juce::ValueTree&, juce::ValueTree&, int)
1413{
1414 triggerUpdate();
1415}
1416
1417void RackType::valueTreeChildOrderChanged (juce::ValueTree&, int, int) {}
1418void RackType::valueTreeRedirected (juce::ValueTree&) { triggerUpdate(); }
1419
1420void RackType::valueTreeParentChanged (juce::ValueTree&)
1421{
1422 if (! state.getParent().isValid())
1423 hideWindowForShutdown();
1424
1425 triggerUpdate();
1426}
1427
1428void RackType::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& ident)
1429{
1430 if (v.hasType (IDs::PLUGININSTANCE) || v.hasType (IDs::CONNECTION))
1431 if (ident != IDs::x && ident != IDs::y && ident != IDs::windowLocked && ident != IDs::windowPos)
1432 triggerUpdate();
1433
1434 if (v == state && ident == IDs::name)
1435 {
1436 rackName.forceUpdateOfCachedValue();
1437
1438 for (auto af : getAllPlugins (edit, false))
1439 if (auto rf = dynamic_cast<RackInstance*> (af))
1440 if (rf->type.get() == this)
1441 rf->changed();
1442
1443 changed();
1444 }
1445
1446 if (v == state && ident == IDs::id)
1448}
1449
1450//==============================================================================
1451RackType::WindowState::WindowState (RackType& r, juce::ValueTree windowStateTree)
1452 : PluginWindowState (r.edit), rack (r), state (std::move (windowStateTree))
1453{}
1454
1455}} // namespace tracktion { inline namespace engine
int size() const noexcept
bool contains(ParameterType elementToLookFor) const
bool isValid() const noexcept
constexpr Point translated(ValueType deltaX, ValueType deltaY) const noexcept
ValueType y
ValueType x
static Rectangle fromString(StringRef stringVersion)
static Result fail(const String &errorMessage) noexcept
static Result ok() noexcept
int size() const noexcept
void remove(int index)
bool hasType(const Identifier &typeName) const noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
ValueTree getChild(int index) const
int getNumChildren() const noexcept
void copyPropertiesFrom(const ValueTree &source, UndoManager *undoManager)
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
ValueTree createCopy() const
void removeListener(Listener *listener)
The Tracktion Edit class!
void restartPlayback()
Use this to tell the play engine to rebuild the audio graph if the toplogy has changed.
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
PluginCache & getPluginCache() noexcept
Returns the PluginCache which manages all active Plugin[s] for this Edit.
bool isLoading() const
Returns true if the Edit's not yet fully loaded.
juce::UndoManager & getUndoManager() noexcept
Returns the juce::UndoManager used for this Edit.
EditItemID createNewItemID() const
Returns a new EditItemID to use for a new EditItem.
bool shouldLoadPlugins() const noexcept
Returns true if this Edit can load Plugin[s].
RackTypeList & getRackList() const noexcept
Returns the RackTypeList which contains all the RackTypes for the Edit.
Engine & engine
A reference to the Engine.
UIBehaviour & getUIBehaviour() const
Returns the UIBehaviour class.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
Base class for elements which can contain macro parameters.
juce::String getSelectableDescription() override
Subclasses must return a description of what they are.
bool addPlugin(const Plugin::Ptr &, juce::Point< float > pos, bool canAutoConnect)
Adds a plugin to the Rack optionally connecting it to the input and outputs.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
void stopIfRecording()
Stops playback only if recording is currently in progress.
virtual void showWarningAlert(const juce::String &title, const juce::String &message)
Should display a dismissable alert window.
virtual void showWarningMessage(const juce::String &message)
Should display a temporary warning message.
T count(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
T min(T... args)
T move(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
std::unique_ptr< XmlElement > parseXML(const String &textToParse)
dontSendNotification
juce::ReferenceCountedArray< AutomatableParameter > getAllParametersBeingModifiedBy(Edit &edit, AutomatableParameter::ModifierSource &m)
Iterates an Edit looking for all parameters that are being modified by the given ModifierSource.
juce::Array< Track * > getAllTracks(const Edit &edit)
Returns all the tracks in an Edit.
Modifier::Ptr findModifierForID(ModifierList &ml, EditItemID modifierID)
Returns a Modifier if it can be found in the list.
Plugin::Array getAllPlugins(const Edit &edit, bool includeMasterVolume)
Returns all the plugins in a given Edit.
T size(T... args)
T store(T... args)
ID for objects of type EditElement - e.g.
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.
T ws(T... args)