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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_Project.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
14// a combined version number and file identifier for the project file
15static const char* magicNumberV1 = "TP01";
16
17//==============================================================================
18Project::Project (Engine& e, ProjectManager& pm, const juce::File& projectFile)
19 : engine (e), projectManager (pm), file (projectFile)
20{
21 jassert (isTracktionProjectFile (file));
22
23 for (auto* p : projectManager.openProjects)
24 {
26 jassert (p->getProjectFile() != file);
27 }
28
29 projectManager.openProjects.add (this);
30
31 lockFile();
32 load();
33}
34
35Project::~Project()
36{
37 projectManager.openProjects.removeFirstMatchingValue (this);
38 save();
39 notifyListenersOfDeletion();
40}
41
42void Project::lockFile()
43{
44 if (fileLockingStream == nullptr)
45 fileLockingStream = file.createInputStream();
46}
47
48void Project::unlockFile()
49{
50 fileLockingStream.reset();
51}
52
53void Project::load()
54{
56 readOnly = ! (file.hasWriteAccess() && ! file.isDirectory());
57 projectId = 0;
58
59 auto in = getInputStream();
60
61 if (in != nullptr && readProjectHeader (*in))
62 {
63 in->setPosition (objectOffset);
64 int num = in->readInt();
65
66 jassert (num >= 0 && num < 20000); // vague sanity check
67
68 if (num < 20000)
69 {
70 while (--num >= 0)
71 {
72 ObjectInfo o;
73 o.itemID = in->readInt();
74 o.fileOffset = in->readInt();
75
76 jassert (o.itemID != 0);
77 jassert (o.fileOffset > 0);
78
79 if (o.fileOffset > 0 && o.itemID != 0)
80 objects.add (o);
81 }
82 }
83 }
84 else
85 {
86 stream = nullptr;
87 projectId = 0;
88 }
89
90 hasChanged = false;
91}
92
93void Project::refreshProjectPropertiesFromFile()
94{
95 stream = nullptr;
96
97 if (auto in = getInputStream())
98 readProjectHeader (*in, false);
99}
100
101bool Project::readProjectHeader (juce::InputStream& in, bool clearObjectInfo)
102{
104
105 if (clearObjectInfo)
106 objects.clear();
107
108 char n[4] = { 0 };
109 in.read (n, 4);
110
111 if (strncmp (n, magicNumberV1, 4) == 0)
112 {
113 projectId = in.readInt();
114 objectOffset = in.readInt();
115 indexOffset = in.readInt();
116
117 int numProps = in.readInt();
118 properties.clear();
119
120 while (--numProps >= 0)
121 {
122 auto propName = in.readString();
123 auto size = in.readInt();
124
125 juce::MemoryBlock mem ((size_t) size);
126 in.read (mem.getData(), size);
127
128 properties.set (propName, mem.toString());
129 }
130
131 return objectOffset > 0 && indexOffset > 0;
132 }
133
134 return false;
135}
136
137bool Project::loadProjectItem (ObjectInfo& o)
138{
139 if (o.fileOffset > 0)
140 {
141 if (auto in = getInputStream())
142 {
143 in->setPosition (o.fileOffset);
144 o.item = new ProjectItem (engine, ProjectItemID (o.itemID, projectId), in);
145 return true;
146 }
147 }
148
150 return false;
151}
152
153void Project::loadAllProjectItems()
154{
156 const juce::ScopedLock sl (objectLock);
157
158 for (auto& o : objects)
159 if (o.item == nullptr)
160 if (! loadProjectItem (o))
161 break;
162}
163
164juce::BufferedInputStream* Project::getInputStream()
165{
166 if (stream == nullptr && file.getSize() > 0)
167 if (auto in = file.createInputStream())
168 stream = std::make_unique<juce::BufferedInputStream> (in.release(), 16384, true);
169
170 return stream.get();
171}
172
173void Project::handleAsyncUpdate()
174{
175 save();
176}
177
178bool Project::save()
179{
181
182 if (isValid() && ! isReadOnly())
183 {
184 if (! hasChanged)
185 return true;
186
187 const juce::ScopedLock sl (objectLock);
188
189 loadAllProjectItems();
190
191 auto tempFile = file.getParentDirectory().getNonexistentChildFile ("temp", ".tmp");
192
193 if (auto out = tempFile.createOutputStream())
194 {
195 saveTo (*out);
196 out.reset();
197
198 stream = nullptr;
199 unlockFile();
200
201 // try this twice
202 if (tempFile.moveFileTo (file) || tempFile.moveFileTo (file))
203 {
204 hasChanged = false;
205
206 DBG (juce::Time::getCurrentTime().toString (false, true)
207 + " Saved: " + file.getFullPathName());
208 }
209 else
210 {
212 hasChanged = true;
213
214 bool b = tempFile.deleteFile();
216
217 DBG ("!!couldn't save " + file.getFullPathName());
218 }
219
220 lockFile();
221 }
222
223 cancelPendingUpdate();
224 return ! hasChanged;
225 }
226
227 return false;
228}
229
230//==============================================================================
231void Project::saveTo (juce::FileOutputStream& out)
232{
233 if (! isValid())
234 return;
235
236 out.write (magicNumberV1, 4);
237 out.writeInt (getProjectID());
238 out.writeInt (0);
239 out.writeInt (0);
240 out.writeInt (properties.size());
241
242 for (int i = 0; i < properties.size(); ++i)
243 {
244 out.writeString (properties.getName(i).toString());
245
246 auto value = properties.getValueAt (i).toString();
247 auto utf8 = value.toUTF8();
248 auto numBytes = value.getNumBytesAsUTF8() + 1;
249
250 out.writeInt ((int) numBytes);
251 out.write (utf8, numBytes);
252 }
253
254 for (auto& o : objects)
255 {
256 if (auto c = o.item)
257 {
258 o.fileOffset = (int) out.getPosition();
259 c->writeToStream (out);
260 }
261 }
262
263 objectOffset = (int) out.getPosition();
264
265 out.writeInt (objects.size());
266
267 for (auto& o : objects)
268 {
269 out.writeInt (o.itemID);
270 out.writeInt (o.fileOffset);
271 }
272
273 indexOffset = (int) out.getPosition();
274 ProjectSearchIndex searchIndex (*this);
275
276 for (auto& o : objects)
277 if (auto c = o.item)
278 searchIndex.addClip (c);
279
280 searchIndex.writeToStream (out);
281
282 out.setPosition (8);
283 out.writeInt (objectOffset);
284 out.writeInt (indexOffset);
285}
286
287//==============================================================================
288bool Project::isValid() const
289{
290 return projectId != 0;
291}
292
293bool Project::isReadOnly() const
294{
295 return readOnly;
296}
297
298int Project::getProjectID() const
299{
300 return projectId;
301}
302
303juce::String Project::getProjectProperty (const juce::String& name) const
304{
305 const juce::ScopedLock sl (propertyLock);
306 return properties [name];
307}
308
309void Project::setProjectProperty (const juce::String& name, const juce::String& value)
310{
311 const juce::ScopedLock sl (propertyLock);
312 properties.set (name, value);
313 changed();
314}
315
316juce::String Project::getName() const
317{
318 return getProjectProperty ("name");
319}
320
321juce::String Project::getDescription() const
322{
323 return getProjectProperty ("description");
324}
325
326void Project::setName (const juce::String& newName)
327{
328 if (getName() != newName)
329 {
330 setProjectProperty ("name", newName.substring (0, 64));
331 engine.getUIBehaviour().updateAllProjectItemLists();
332
333 auto dst = file.getParentDirectory().getChildFile (juce::File::createLegalFileName (newName)
334 + file.getFileExtension());
335 stream = nullptr;
336
337 unlockFile();
338
339 if (file.moveFileTo (dst) || file.moveFileTo (dst))
340 file = dst;
341
342 projectManager.updateProjectFile (*this, file);
343 lockFile();
344
345 projectManager.saveList();
346 }
347}
348
349void Project::setDescription (const juce::String& newDesc)
350{
351 setProjectProperty ("description", juce::String (newDesc).substring (0, 512));
352}
353
354void Project::createNewProjectId()
355{
356 auto newID = juce::Random::getSystemRandom().nextInt (9999999);
357
358 while (projectManager.getProject (newID))
359 {
361 newID = juce::Random::getSystemRandom().nextInt (9999999);
362 }
363
364 projectId = newID;
365 hasChanged = true;
366}
367
368void Project::redirectIDsFromProject (int oldProjId, int newProjId)
369{
370 for (int k = 0; k < getNumProjectItems(); ++k)
371 {
372 if (auto mo = getProjectItemAt (k))
373 {
374 if (mo->isEdit())
375 {
376 auto ed = loadEditForExamining (projectManager, mo->getID());
377
378 for (auto exportable : Exportable::addAllExportables (*ed))
379 {
380 for (auto& item : exportable->getReferencedItems())
381 {
382 if (item.itemID.getProjectID() == oldProjId)
383 exportable->reassignReferencedItem (item, item.itemID.withNewProjectID (newProjId), 0.0);
384 }
385 }
386
387 EditFileOperations (*ed).save (false, true, false);
388 }
389 }
390 }
391
392 changed();
393}
394
395bool Project::isLibraryProject() const
396{
397 return projectManager.findFolderContaining (*this) == projectManager.getLibraryProjectsFolder();
398}
399
400void Project::changed()
401{
402 hasChanged = true;
403 triggerAsyncUpdate();
404 Selectable::changed();
405}
406
407int Project::getNumProjectItems()
408{
409 return objects.size();
410}
411
412ProjectItemID Project::getProjectItemID (int i)
413{
414 const juce::ScopedLock sl (objectLock);
415
416 if (juce::isPositiveAndBelow (i, objects.size()))
417 return ProjectItemID (objects.getReference(i).itemID, projectId);
418
419 return {};
420}
421
422juce::Array<int> Project::getAllItemIDs() const
423{
425
426 for (auto& o : objects)
427 a.add (o.itemID);
428
429 return a;
430}
431
432juce::Array<ProjectItemID> Project::getAllProjectItemIDs() const
433{
435
436 const juce::ScopedLock sl (objectLock);
437
438 for (auto& o : objects)
439 dest.add (ProjectItemID (o.itemID, projectId));
440
441 return dest;
442}
443
444ProjectItem::Ptr Project::getProjectItemAt (int i)
445{
446 const juce::ScopedLock sl (objectLock);
447
448 if (juce::isPositiveAndBelow (i, objects.size()))
449 {
450 auto& o = objects.getReference(i);
451
452 if (o.item == nullptr)
453 loadProjectItem (o);
454
455 return o.item;
456 }
457
458 return {};
459}
460
461juce::Array<ProjectItem::Ptr> Project::getAllProjectItems()
462{
464
465 const juce::ScopedLock sl (objectLock);
466
467 for (auto& o : objects)
468 {
469 if (o.item == nullptr)
470 loadProjectItem (o);
471
472 dest.add (o.item);
473 }
474
475 return dest;
476}
477
478ProjectItem::Ptr Project::getProjectItemForID (ProjectItemID targetId)
479{
480 const juce::ScopedLock sl (objectLock);
481 return getProjectItemAt (getIndexOf (targetId));
482}
483
484ProjectItem::Ptr Project::getProjectItemForFile (const juce::File& fileToFind)
485{
486 const juce::ScopedLock sl (objectLock);
487
488 for (auto& o : objects)
489 {
490 if (o.item == nullptr)
491 if (! loadProjectItem (o))
492 continue;
493
494 if (o.item->isForFile (fileToFind))
495 return o.item;
496 }
497
498 return {};
499}
500
501int Project::getIndexOf (ProjectItemID mo) const
502{
503 const juce::ScopedLock sl (objectLock);
504
505 if (mo.getProjectID() == getProjectID())
506 {
507 auto itemID = mo.getItemID();
508
509 for (int i = objects.size(); --i >= 0;)
510 if (objects.getReference(i).itemID == itemID)
511 return i;
512 }
513
514 return -1;
515}
516
517void Project::moveProjectItem (int indexToMoveFrom, int indexToMoveTo)
518{
519 if (indexToMoveTo != indexToMoveFrom)
520 {
521 const juce::ScopedLock sl (objectLock);
522
523 if (indexToMoveFrom >= 0 && indexToMoveFrom < objects.size())
524 {
525 objects.move (indexToMoveFrom, juce::jlimit (0, objects.size(), indexToMoveTo));
526 changed();
527 }
528 }
529}
530
531ProjectItem::Ptr Project::createNewItem (const juce::File& fileToReference,
532 const juce::String& type,
533 const juce::String& name,
534 const juce::String& description,
535 const ProjectItem::Category cat,
536 bool atTopOfList)
537{
538 jassert (type.isNotEmpty());
539
540 if (isValid() && ! isReadOnly())
541 {
542 if (auto mo = getProjectItemForFile (fileToReference))
543 if (mo->getID().isValid() && mo->getType() == type)
544 return mo;
545
546 ObjectInfo o;
547 o.item = new ProjectItem (engine, name, type, description, {}, cat, 0,
548 ProjectItemID::createNewID (getProjectID()));
549 o.itemID = o.item->getID().getItemID();
550 o.fileOffset = 0;
551
552 {
553 const juce::ScopedLock sl (objectLock);
554
555 if (atTopOfList)
556 objects.insert (0, o);
557 else
558 objects.add (o);
559 }
560
561 o.item->setSourceFile (fileToReference);
562 o.item->verifyLength();
563
564 changed();
565 return o.item;
566 }
567
568 return {};
569}
570
571ProjectItem::Ptr Project::quickAddProjectItem (const juce::String& relPathName,
572 const juce::String& type,
573 const juce::String& name,
574 const juce::String& description,
575 const ProjectItem::Category cat,
576 ProjectItemID newID)
577{
578 ObjectInfo o;
579 o.item = new ProjectItem (engine, name, type, description, {}, cat, 0, newID);
580 o.itemID = o.item->getID().getItemID();
581 o.fileOffset = 0;
582 o.item->file = relPathName;
583
584 {
585 const juce::ScopedLock sl (objectLock);
586 objects.add (o);
587 }
588
589 changed();
590 return o.item;
591}
592
593bool Project::removeProjectItem (ProjectItemID item, bool deleteSourceMaterial)
594{
595 if (isValid() && ! isReadOnly())
596 {
597 {
598 const juce::ScopedLock sl (objectLock);
599
600 const int index = getIndexOf (item);
601 jassert (index >= 0);
602
603 if (index >= 0)
604 {
605 auto& o = objects.getReference (index);
606
607 if (o.item != nullptr)
608 {
609 o.item->deselect();
610
611 if (deleteSourceMaterial)
612 if (! o.item->deleteSourceFile())
613 return false;
614 }
615
616 objects.remove (index);
617 }
618 }
619
620 changed();
621 return true;
622 }
623
624 return false;
625}
626
627juce::File Project::getDirectoryForMedia (ProjectItem::Category category) const
628{
629 auto dir = getDefaultDirectory();
630
631 switch (category)
632 {
633 case ProjectItem::Category::archives: dir = dir.getChildFile (TRANS("Archived")); break;
634 case ProjectItem::Category::exports: dir = dir.getChildFile (TRANS("Exported")); break;
635 case ProjectItem::Category::frozen: dir = dir.getChildFile (TRANS("Frozen")); break;
636 case ProjectItem::Category::imported: dir = dir.getChildFile (TRANS("Imported")); break;
637 case ProjectItem::Category::recorded: dir = dir.getChildFile (TRANS("Recorded")); break;
638 case ProjectItem::Category::rendered: dir = dir.getChildFile (TRANS("Rendered")); break;
639 case ProjectItem::Category::video: dir = dir.getChildFile (TRANS("Movies")); break;
640
641 case ProjectItem::Category::edit:
642 case ProjectItem::Category::none:
643 break;
644 }
645
646 if (! dir.isDirectory())
647 dir.createDirectory();
648
649 return dir;
650}
651
652juce::File Project::getDefaultDirectory() const
653{
654 return file.getParentDirectory();
655}
656
657ProjectItem::Ptr Project::createNewEdit()
658{
659 int maxSuffix = 0;
660
661 for (int i = 0; i < getNumProjectItems(); ++i)
662 {
663 if (auto p = getProjectItemAt (i))
664 {
665 if (p->isEdit())
666 {
667 auto nm = p->getName();
668
669 if (nm.startsWithIgnoreCase (getName() + " Edit "))
670 maxSuffix = std::max (maxSuffix, nm.getTrailingIntValue());
671 }
672 }
673 }
674
675 auto name = getName() + " Edit ";
676 name << (maxSuffix + 1);
677
678 auto f = getDefaultDirectory().getNonexistentChildFile (name, editFileSuffix, false);
679
680 if (f.create())
681 return createNewItem (f, ProjectItem::editItemType(), name,
682 {}, ProjectItem::Category::edit, true);
683
684 return {};
685}
686
687void Project::searchFor (juce::Array<ProjectItemID>& results, SearchOperation& searchOp)
688{
689 save();
690
691 if (indexOffset > 0)
692 {
693 ProjectSearchIndex psi (*this);
694
695 {
696 const juce::ScopedLock sl (objectLock);
697
698 if (auto in = getInputStream())
699 {
700 in->setPosition (indexOffset);
701 psi.readFromStream (*in);
702 }
703 }
704
705 psi.findMatches (searchOp, results);
706 }
707}
708
709void Project::mergeArchiveContents (const juce::File& archiveFile)
710{
711 TracktionArchiveFile archive (engine, archiveFile);
712
713 if (! archive.isValidArchive())
714 {
715 engine.getUIBehaviour().showWarningMessage (TRANS("This file wasn't a valid tracktion archive file"));
716 return;
717 }
718
719 bool wasAborted;
721
722 if (archive.extractAllAsTask (getProjectFile().getParentDirectory(), true, newFiles, wasAborted))
723 {
724 if (! wasAborted)
725 {
726 for (const auto& f : newFiles)
727 {
728 if (isTracktionProjectFile (f))
729 {
730 mergeOtherProjectIntoThis (f);
731 f.deleteFile();
732
733 jassert (! f.exists());
734 }
735 }
736
737 refreshFolderStructure();
738 }
739 }
740 else
741 {
742 engine.getUIBehaviour().showWarningMessage (TRANS("Errors occurred whilst trying to unpack this archive"));
743 }
744}
745
746void Project::mergeOtherProjectIntoThis (const juce::File& f)
747{
748 ProjectManager::TempProject temp (projectManager, f, false);
749
750 if (auto p = temp.project)
751 {
752 if (p->isValid())
753 {
754 for (int i = 0; i < p->getNumProjectItems(); ++i)
755 {
756 if (auto src = p->getProjectItemAt (i))
757 {
758 if (auto mo = quickAddProjectItem (src->file,
759 src->getType(),
760 src->getName(),
761 src->description,
762 src->getCategory(),
763 src->getID()))
764 {
765 mo->copyAllPropertiesFrom (*src);
766 mo->verifyLength();
767 mo->changeProjectId (p->getProjectID(), getProjectID());
768 }
769 }
770 }
771 }
772 }
773}
774
775juce::Array<ProjectItemID> Project::findOrphanItems()
776{
777 const juce::ScopedLock sl (objectLock);
778
779 auto unreffed = getAllProjectItemIDs();
780
781 for (int j = 0; j < getNumProjectItems(); ++j)
782 {
783 if (unreffed.isEmpty())
784 break;
785
786 if (auto mo = getProjectItemAt (j))
787 {
788 if (mo->isEdit())
789 {
790 auto ed = loadEditForExamining (projectManager, mo->getID());
791
792 for (int i = unreffed.size(); --i >= 0;)
793 if (referencesProjectItem (*ed, unreffed.getReference(i)))
794 unreffed.remove (i);
795 }
796 }
797 }
798
799 return unreffed;
800}
801
802juce::String Project::getSelectableDescription()
803{
804 return isReadOnly() ? TRANS("Read-Only Project")
805 : TRANS("Project");
806}
807
808bool Project::askAboutTempoDetect (const juce::File& f, bool& shouldSetAutoTempo) const
809{
810 #if JUCE_MODAL_LOOPS_PERMITTED
811 NagMode im = (NagMode) static_cast<int> (engine.getPropertyStorage().getProperty (SettingID::autoTempoDetect, (int) nagAsk));
812
813 shouldSetAutoTempo = engine.getPropertyStorage().getProperty (SettingID::autoTempoMatch, false);
814
815 if (im == nagAutoYes)
816 return true;
817
818 if (im == nagAutoNo)
819 return false;
820
821 juce::ToggleButton autoTempo (TRANS("Set tempo to match project"));
822 autoTempo.setToggleState (shouldSetAutoTempo, juce::dontSendNotification);
823 autoTempo.setSize (200, 20);
824
825 juce::ToggleButton dontAsk (TRANS("Remember my choice"));
826 dontAsk.setSize (200, 20);
827
829 .createAlertWindow (TRANS("Detect Tempo?"),
830 TRANS("No tempo information was found in XZZX, would you like to detect it automatically?")
831 .replace ("XZZX", f.getFileNameWithoutExtension()),
832 {}, {}, {}, juce::AlertWindow::QuestionIcon, 0, nullptr));
833
834 w->addCustomComponent (&autoTempo);
835 w->addCustomComponent (&dontAsk);
836 w->addButton (TRANS("Yes"), 1, juce::KeyPress (juce::KeyPress::returnKey));
837 w->addButton (TRANS("No"), 0, juce::KeyPress (juce::KeyPress::escapeKey));
838
839 const int res = w->runModalLoop();
840
841 shouldSetAutoTempo = autoTempo.getToggleState();
842 engine.getPropertyStorage().setProperty (SettingID::autoTempoMatch, shouldSetAutoTempo);
843
844 if (dontAsk.getToggleState())
845 engine.getPropertyStorage().setProperty (SettingID::autoTempoDetect, (int) (res == 1 ? nagAutoYes : nagAutoNo));
846
847 return res == 1;
848 #else
849 juce::ignoreUnused (f, shouldSetAutoTempo);
850 return true;
851 #endif
852}
853
854void Project::ensureFolderCreated (ProjectItem::Category c)
855{
856 auto dir = getDirectoryForMedia (c);
857
858 if (! dir.isDirectory())
859 dir.createDirectory();
860}
861
862void Project::createDefaultFolders()
863{
864 ensureFolderCreated (ProjectItem::Category::archives);
865 ensureFolderCreated (ProjectItem::Category::exports);
866 ensureFolderCreated (ProjectItem::Category::imported);
867 ensureFolderCreated (ProjectItem::Category::recorded);
868 ensureFolderCreated (ProjectItem::Category::rendered);
869 ensureFolderCreated (ProjectItem::Category::edit);
870}
871
872void Project::refreshFolderStructure()
873{
874 auto projDir = getProjectFile().getParentDirectory();
875
876 for (auto& item : getAllProjectItemIDs())
877 {
878 if (auto mo = getProjectItemForID (item))
879 {
880 auto srcFile = mo->getSourceFile();
881 auto dstDir = getDirectoryForMedia (mo->getCategory());
882
883 if (! dstDir.isDirectory())
884 dstDir.createDirectory();
885
886 if (srcFile.isAChildOf (projDir))
887 {
888 auto dstFile = dstDir.getChildFile (srcFile.getFileName());
889
890 if (srcFile.moveFileTo (dstFile))
891 mo->setSourceFile (dstFile);
892 }
893 }
894 else
895 {
897 }
898 }
899}
900
901}} // namespace tracktion { inline namespace engine
void add(const ElementType &newElement)
bool write(const void *, size_t) override
bool setPosition(int64) override
int64 getPosition() override
String getFileNameWithoutExtension() const
static String createLegalFileName(const String &fileNameToFix)
bool isValid() const noexcept
const String & toString() const noexcept
virtual bool setPosition(int64 newPosition)=0
virtual int read(void *destBuffer, int maxBytesToRead)=0
virtual int readInt()
virtual String readString()
static const int escapeKey
static const int returnKey
static LookAndFeel & getDefaultLookAndFeel() noexcept
virtual bool writeInt(int value)
virtual bool writeString(const String &text)
int nextInt() noexcept
static Random & getSystemRandom() noexcept
String substring(int startIndex, int endIndex) const
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Contains methods for saving an Edit to a file.
An ID representing one of the items in a Project.
int getItemID() const
Returns the ID of the item within the project.
Represents a file-based resource that is used in a project.
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define DBG(textToWrite)
#define jassertfalse
typedef int
T max(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
dontSendNotification
void ignoreUnused(Types &&...) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
std::unique_ptr< Edit > loadEditForExamining(ProjectManager &pm, ProjectItemID itemID, Edit::EditRole role)
Uses the ProjectManager to find an Edit file and open it.
T size(T... args)
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.