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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ValueTreeUtilities.h
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 juce
12{
13 template <>
15 {
16 static Colour fromVar (const var& v) { return Colour::fromString (v.toString()); }
17 static var toVar (const Colour& c) { return c.toString(); }
18 };
19}
20
21namespace tracktion { inline namespace engine
22{
23
24//==============================================================================
28template<typename VarType>
30{
31 if (const auto* prop = v.getPropertyPointer (id))
32 if (prop->isString())
33 (*const_cast<juce::var*> (prop)) = static_cast<VarType> (*prop);
34}
35
37template <typename T>
38static bool setIfDifferent (T& val, T newVal) noexcept
39{
40 if (val == newVal)
41 return false;
42
43 val = newVal;
44 return true;
45}
46
48template<typename Type>
49bool matchesAnyOf (const Type& needle, const std::initializer_list<Type>& haystack)
50{
51 for (auto&& h : haystack)
52 if (h == needle)
53 return true;
54
55 return false;
56}
57
59template<typename Type, typename UnaryFunction>
60inline void forEachItem (const juce::Array<Type*>& items, const UnaryFunction& fn)
61{
62 for (auto f : items)
63 fn (f);
64}
65
66//==============================================================================
67template <typename... Others>
68void addValueTreeProperties (juce::ValueTree& v, const juce::Identifier& name, const juce::var& value, Others&&... others)
69{
70 static_assert ((sizeof...(others) & 1) == 0, "The property list must be a sequence of name, value pairs");
71
72 v.setProperty (name, value, nullptr);
73
74 if constexpr (sizeof...(others) != 0)
75 addValueTreeProperties (v, std::forward<Others> (others)...);
76}
77
78template <typename... Properties>
79juce::ValueTree createValueTree (const juce::Identifier& name, Properties&&... properties)
80{
81 static_assert ((sizeof...(properties) & 1) == 0, "The property list must be a sequence of name, value pairs");
82
83 juce::ValueTree v (name);
84 addValueTreeProperties (v, std::forward<Properties> (properties)...);
85 return v;
86}
87
88
89//==============================================================================
90template<typename ObjectType, typename CriticalSectionType = juce::DummyCriticalSection>
92{
93public:
94 ValueTreeObjectList (const juce::ValueTree& parentTree) : parent (parentTree)
95 {
96 parent.addListener (this);
97 }
98
99 ~ValueTreeObjectList() override
100 {
101 jassert (objects.isEmpty()); // must call freeObjects() in the subclass destructor!
102 }
103
104 inline int size() const { return objects.size(); }
105 inline bool isEmpty() const noexcept { return size() == 0; }
106 ObjectType* operator[] (int idx) const { return objects[idx]; }
107 ObjectType* at (int idx) { return objects[idx]; }
108 ObjectType** begin() { return objects.begin(); }
109 ObjectType* const* begin() const { return objects.begin(); }
110 ObjectType** end() { return objects.end(); }
111 ObjectType* const* end() const { return objects.end(); }
112
113 // call in the sub-class when being created
114 void rebuildObjects()
115 {
116 jassert (objects.isEmpty()); // must only call this method once at construction
117
118 for (const auto& v : parent)
119 if (isSuitableType (v))
120 if (auto newObject = createNewObject (v))
121 objects.add (newObject);
122 }
123
124 // call in the sub-class when being destroyed
125 void freeObjects()
126 {
127 parent.removeListener (this);
128 deleteAllObjects();
129 }
130
131 //==============================================================================
132 virtual bool isSuitableType (const juce::ValueTree&) const = 0;
133 virtual ObjectType* createNewObject (const juce::ValueTree&) = 0;
134 virtual void deleteObject (ObjectType*) = 0;
135
136 virtual void newObjectAdded (ObjectType*) = 0;
137 virtual void objectRemoved (ObjectType*) = 0;
138 virtual void objectOrderChanged() = 0;
139
140 //==============================================================================
141 void valueTreeChildAdded (juce::ValueTree&, juce::ValueTree& tree) override
142 {
143 if (isChildTree (tree))
144 {
145 auto index = parent.indexOf (tree);
146 juce::ignoreUnused (index);
147 jassert (index >= 0);
148
149 if (auto* newObject = createNewObject (tree))
150 {
151 {
152 const ScopedLockType sl (arrayLock);
153
154 if (index == parent.getNumChildren() - 1)
155 objects.add (newObject);
156 else
157 objects.addSorted (*this, newObject);
158 }
159
160 newObjectAdded (newObject);
161 }
162 else
164 }
165 }
166
167 void valueTreeChildRemoved (juce::ValueTree& exParent, juce::ValueTree& tree, int) override
168 {
169 if (parent == exParent && isSuitableType (tree))
170 {
171 auto oldIndex = indexOf (tree);
172
173 if (oldIndex >= 0)
174 {
175 ObjectType* o;
176
177 {
178 const ScopedLockType sl (arrayLock);
179 o = objects.removeAndReturn (oldIndex);
180 }
181
182 objectRemoved (o);
183 deleteObject (o);
184 }
185 }
186 }
187
188 void valueTreeChildOrderChanged (juce::ValueTree& tree, int, int) override
189 {
190 if (tree == parent)
191 {
192 {
193 const ScopedLockType sl (arrayLock);
194 sortArray();
195 }
196
197 objectOrderChanged();
198 }
199 }
200
201 void valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier&) override {}
202 void valueTreeParentChanged (juce::ValueTree&) override {}
203
204 void valueTreeRedirected (juce::ValueTree&) override { jassertfalse; } // may need to add handling if this is hit
205
207 CriticalSectionType arrayLock;
208 using ScopedLockType = typename CriticalSectionType::ScopedLockType;
209
210protected:
211 juce::ValueTree parent;
212
213 void deleteAllObjects()
214 {
215 const ScopedLockType sl (arrayLock);
216
217 while (objects.size() > 0)
218 deleteObject (objects.removeAndReturn (objects.size() - 1));
219 }
220
221 bool isChildTree (juce::ValueTree& v) const
222 {
223 return isSuitableType (v) && v.getParent() == parent;
224 }
225
226 int indexOf (const juce::ValueTree& v) const noexcept
227 {
228 for (int i = 0; i < objects.size(); ++i)
229 if (objects.getUnchecked(i)->state == v)
230 return i;
231
232 return -1;
233 }
234
235 void sortArray()
236 {
237 objects.sort (*this);
238 }
239
240public:
241 int compareElements (ObjectType* first, ObjectType* second) const
242 {
243 int index1 = parent.indexOf (first->state);
244 int index2 = parent.indexOf (second->state);
245 jassert (index1 >= 0 && index2 >= 0);
246 return index1 - index2;
247 }
248
250};
251
252//==============================================================================
253template<typename ObjectType>
254struct SortedValueTreeObjectList : protected ValueTreeObjectList<ObjectType>
255{
258 {
259 }
260
262 virtual void sortObjects (juce::Array<ObjectType*>& objectsToBeSorted) const = 0;
263
269
273 virtual bool objectsAreSorted (const ObjectType& first, const ObjectType& second) = 0;
274
275 //==============================================================================
278 {
279 TRACKTION_ASSERT_MESSAGE_THREAD
280 jassert (! insidePropertyChangedMethod);
281
282 if (setIfDifferent (needsSorting, false))
283 {
285 sortObjects (sortedObjects);
286 }
287
288 return sortedObjects;
289 }
290
291 //==============================================================================
292 // Make sure you call the base class implementation of these methods if you override them.
293 void newObjectAdded (ObjectType* o) override { triggerSortIfNeeded (o, -1); }
294 void objectOrderChanged() override { triggerSort(); }
295
296 void valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& id) override
297 {
298 const juce::ScopedValueSetter<bool> svs (insidePropertyChangedMethod, true);
299
300 if (! (this->isChildTree (v) && isSortableProperty (v, id)))
301 return;
302
303 const int objectIndex = getSortedIndex (v);
304 triggerSortIfNeeded (sortedObjects.getUnchecked (objectIndex), objectIndex);
305 }
306
307private:
308 mutable bool needsSorting = true;
309 mutable juce::Array<ObjectType*> sortedObjects;
310 bool insidePropertyChangedMethod = false;
311
312 int getSortedIndex (const juce::ValueTree& v) const noexcept
313 {
314 for (int i = sortedObjects.size(); --i >= 0;)
315 if (sortedObjects.getUnchecked (i)->state == v)
316 return i;
317
318 return -1;
319 }
320
321 void triggerSort() const
322 {
323 TRACKTION_ASSERT_MESSAGE_THREAD
324 needsSorting = true;
325 }
326
327 void triggerSortIfNeeded (ObjectType* o, int objectIndex)
328 {
329 if (needsSorting)
330 return;
331
332 if (objectIndex < 0)
333 return triggerSort();
334
335 jassert (o != nullptr);
336
337 if (objectIndex > 0
338 && (! objectsAreSorted (*sortedObjects.getUnchecked (objectIndex - 1), *o)))
339 return triggerSort();
340
341 if (objectIndex < (sortedObjects.size() - 1)
342 && (! objectsAreSorted (*o, *sortedObjects.getUnchecked (objectIndex + 1))))
343 return triggerSort();
344 }
345
346 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SortedValueTreeObjectList)
347};
348
349//==============================================================================
351template<typename ObjectType>
352static ObjectType* getObjectFor (const ValueTreeObjectList<ObjectType>& objectList, const juce::ValueTree& v)
353{
354 for (auto* o : objectList.objects)
355 if (o->state == v)
356 return o;
357
358 return {};
359}
360
361//==============================================================================
362// Easy way to get one callback for all value tree change events
364{
365 virtual void valueTreeChanged() = 0;
366
367 //==============================================================================
368 void valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier&) override { valueTreeChanged(); }
369 void valueTreeChildAdded (juce::ValueTree&, juce::ValueTree&) override { valueTreeChanged(); }
370 void valueTreeChildRemoved (juce::ValueTree&, juce::ValueTree&, int) override { valueTreeChanged(); }
371 void valueTreeChildOrderChanged (juce::ValueTree&, int, int) override { valueTreeChanged(); }
372 void valueTreeParentChanged (juce::ValueTree&) override { valueTreeChanged(); }
373 void valueTreeRedirected (juce::ValueTree&) override { valueTreeChanged(); }
374};
375
376template<typename Type>
378{
379public:
380 ValueTreeComparator (const juce::Identifier& attributeToSort_, bool forwards)
381 : attributeToSort (attributeToSort_), direction (forwards ? 1 : -1)
382 {}
383
384 inline int compareElements (const juce::ValueTree& first, const juce::ValueTree& second) const
385 {
386 const int result = (Type (first[attributeToSort]) > Type (second[attributeToSort])) ? 1 : -1;
387
388 return direction * result;
389 }
390
391private:
392 const juce::Identifier attributeToSort;
393 const int direction;
394
396};
397
398template<>
400{
401 const int result = first[attributeToSort].toString().compareNatural (second[attributeToSort].toString());
402
403 return direction * result;
404}
405
406//==============================================================================
414{
415public:
417 ReferenceCountedValueTree (const juce::ValueTree& treeToReference) noexcept
418 : tree (treeToReference)
419 {}
420
423
425 void setValueTree (juce::ValueTree newTree) { tree = newTree; }
426
428 juce::ValueTree getValueTree() noexcept { return tree; }
429
431
435 static juce::ValueTree getTreeFromObject (const juce::var& treeObject) noexcept
436 {
437 if (auto refTree = dynamic_cast<ReferenceCountedValueTree*> (treeObject.getObject()))
438 return refTree->getValueTree();
439
440 return {};
441 }
442
443private:
444 juce::ValueTree tree;
446};
447
448//==============================================================================
450static inline juce::ValueTree loadValueTree (const juce::File& file, bool asXml)
451{
452 if (asXml)
453 {
454 if (auto xml = juce::parseXML (file))
455 return juce::ValueTree::fromXml (*xml);
456 }
457 else
458 {
459 juce::FileInputStream is (file);
460
461 if (is.openedOk())
462 return juce::ValueTree::readFromStream (is);
463 }
464
465 return {};
466}
467
469static inline bool saveValueTree (const juce::File& file, const juce::ValueTree& v, bool asXml)
470{
471 const juce::TemporaryFile temp (file);
472
473 {
474 juce::FileOutputStream os (temp.getFile());
475
476 if (! os.getStatus().wasOk())
477 return false;
478
479 if (asXml)
480 {
481 if (auto xml = v.createXml())
482 xml->writeTo (os);
483 }
484 else
485 {
486 v.writeToStream (os);
487 }
488 }
489
490 if (temp.getFile().existsAsFile())
491 return temp.overwriteTargetFileWithTemporary();
492
493 return false;
494}
495
496static inline void setPropertyIfMissing (juce::ValueTree& v, const juce::Identifier& id,
497 const juce::var& value, juce::UndoManager* um)
498{
499 if (! v.hasProperty (id))
500 v.setProperty (id, value, um);
501}
502
504static inline juce::ValueTree copyValueTree (juce::ValueTree& dest, const juce::ValueTree& src, juce::UndoManager* um)
505{
506 if (! dest.getParent().isValid())
507 {
508 dest = src.createCopy();
509 }
510 else
511 {
512 dest.copyPropertiesFrom (src, um);
513 dest.removeAllChildren (um);
514
515 for (int i = 0; i < src.getNumChildren(); ++i)
516 dest.addChild (src.getChild (i).createCopy(), i, um);
517 }
518
519 return dest;
520}
521
522template <typename Predicate>
523static inline void copyValueTreeProperties (juce::ValueTree& dest, const juce::ValueTree& src,
524 juce::UndoManager* um, Predicate&& shouldCopyProperty)
525{
526 for (int i = 0; i < src.getNumProperties(); ++i)
527 {
528 auto name = src.getPropertyName (i);
529
530 if (shouldCopyProperty (name))
531 dest.setProperty (name, src.getProperty (name), um);
532 }
533}
534
535static inline juce::ValueTree getChildWithPropertyRecursively (const juce::ValueTree& p,
536 const juce::Identifier& propertyName,
537 const juce::var& propertyValue)
538{
539 const int numChildren = p.getNumChildren();
540
541 for (int i = 0; i < numChildren; ++i)
542 {
543 auto c = p.getChild (i);
544
545 if (c.getProperty (propertyName) == propertyValue)
546 return c;
547
548 c = getChildWithPropertyRecursively (c, propertyName, propertyValue);
549
550 if (c.isValid())
551 return c;
552 }
553
554 return {};
555}
556
557template<typename ValueType, typename... CachedValues>
558static void copyPropertiesToCachedValues (const juce::ValueTree& v, juce::CachedValue<ValueType>& cachedValue, CachedValues&&... cachedValues)
559{
560 if (auto p = v.getPropertyPointer (cachedValue.getPropertyID()))
561 cachedValue = ValueType (*p);
562 else
563 cachedValue.resetToDefault();
564
565 if constexpr (sizeof...(cachedValues) != 0)
566 copyPropertiesToCachedValues (v, std::forward<CachedValues> (cachedValues)...);
567}
568
571{
572 for (int i = trees.size(); --i >= 0;)
573 if (! trees.getReference (i).isValid())
574 trees.remove (i);
575
576 return trees;
577}
578
581 const juce::Identifier& type)
582{
584
585 for (const auto& v : trees)
586 if (v.hasType (type))
587 newTrees.add (v);
588
589 return newTrees;
590}
591
592inline void getChildTreesRecursive (juce::Array<juce::ValueTree>& result,
593 const juce::ValueTree& tree)
594{
595 for (auto child : tree)
596 {
597 result.add (child);
598 getChildTreesRecursive (result, child);
599 }
600}
601
602inline void renamePropertyRecursive (juce::ValueTree& tree,
603 const juce::Identifier& oldName,
604 const juce::Identifier& newName,
606{
607 if (auto oldProp = tree.getPropertyPointer (oldName))
608 {
609 tree.setProperty (newName, *oldProp, um);
610 tree.removeProperty (oldName, um);
611 }
612
613 for (auto child : tree)
614 renamePropertyRecursive (child, oldName, newName, um);
615}
616
617}} // namespace tracktion { inline namespace engine
ElementType getUnchecked(int index) const
bool isEmpty() const noexcept
int size() const noexcept
void remove(int indexToRemove)
ElementType * begin() noexcept
ElementType * end() noexcept
void add(const ElementType &newElement)
ElementType removeAndReturn(int indexToRemove)
int addSorted(ElementComparator &comparator, ParameterType newElement)
ElementType & getReference(int index) noexcept
const Identifier & getPropertyID() const noexcept
static Colour fromString(StringRef encodedColourString)
bool isValid() const noexcept
const String & toString() const noexcept
int getNumChildren() const noexcept
void copyPropertiesFrom(const ValueTree &source, UndoManager *undoManager)
bool isValid() const noexcept
const var * getPropertyPointer(const Identifier &name) const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void removeAllChildren(UndoManager *undoManager)
void addListener(Listener *listener)
int indexOf(const ValueTree &child) const noexcept
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
ValueTree getParent() const noexcept
void removeListener(Listener *listener)
void removeProperty(const Identifier &name, UndoManager *undoManager)
Holds a ValueTree as a ReferenceCountedObject.
juce::ValueTree getValueTree() noexcept
Returns the ValueTree being held.
static juce::ValueTree getTreeFromObject(const juce::var &treeObject) noexcept
Provides a simple way of getting the tree from a var object which is a ReferencedCountedValueTree.
ReferenceCountedValueTree(const juce::ValueTree &treeToReference) noexcept
Creates a ReferenceCountedValueTree for a given ValueTree.
void setValueTree(juce::ValueTree newTree)
Sets the ValueTree being held.
T is_pointer_v
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
std::unique_ptr< XmlElement > parseXML(const String &textToParse)
void ignoreUnused(Types &&...) noexcept
void forEachItem(const juce::Array< Type * > &items, const UnaryFunction &fn)
Calls a function object on each item in an array.
juce::Array< juce::ValueTree > getTreesOfType(const juce::Array< juce::ValueTree > &trees, const juce::Identifier &type)
Returns a new array with any trees without the given type removed.
void convertPropertyToType(juce::ValueTree &v, const juce::Identifier &id)
Ensures a property is a given type which can avoid having to parse a string every time it is read aft...
bool matchesAnyOf(const Type &needle, const std::initializer_list< Type > &haystack)
Returns true if the needle is found in the haystack.
juce::Array< juce::ValueTree > & removeInvalidValueTrees(juce::Array< juce::ValueTree > &trees)
Strips out any invalid trees from the array.
const juce::Array< ObjectType * > & getSortedObjects() const
Returns the object for a given state.
virtual bool isSortableProperty(juce::ValueTree &, const juce::Identifier &)=0
Should return true if the given Identifier is used to sort the tree.
virtual void sortObjects(juce::Array< ObjectType * > &objectsToBeSorted) const =0
Must sort the given array.
virtual bool objectsAreSorted(const ObjectType &first, const ObjectType &second)=0
Should return true if the objects are in a sorted order.