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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_EditSnapshot.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//==============================================================================
16{
17 EditSnapshotList() = default;
18
19 EditSnapshot::Ptr getEditSnapshot (ProjectItemID itemID)
20 {
21 const juce::ScopedLock sl (getLock());
22
23 for (auto s : snapshots)
24 if (s->getID() == itemID)
25 return s;
26
27 return {};
28 }
29
30 void addSnapshot (EditSnapshot& snapshot)
31 {
32 snapshots.addIfNotAlreadyThere (&snapshot);
33 }
34
35 void removeSnapshot (EditSnapshot& snapshot)
36 {
37 snapshots.removeAllInstancesOf (&snapshot);
38 }
39
40 const juce::CriticalSection& getLock()
41 {
42 return snapshots.getLock();
43 }
44
45private:
48};
49
50//==============================================================================
55
56//==============================================================================
57EditSnapshot::Ptr EditSnapshot::getEditSnapshot (Engine& engine, ProjectItemID itemID)
58{
60 const juce::ScopedLock sl (list->getLock());
61
62 if (itemID.isInvalid())
63 return {};
64
65 if (auto snapshot = list->getEditSnapshot (itemID))
66 return snapshot;
67
68 return new EditSnapshot (engine, itemID);
69}
70
71//==============================================================================
72EditSnapshot::EditSnapshot (Engine& e, ProjectItemID pid)
73 : engine (e),
74 listHolder (std::make_unique<ListHolder>()),
75 itemID (pid)
76{
77 listHolder->list->addSnapshot (*this);
79}
80
81EditSnapshot::~EditSnapshot()
82{
83 // TODO: Fix this race on destruction as getEditSnapshot may return this at this point
84 listHolder->list->removeSnapshot (*this);
85}
86
88{
89 if (state.getReferenceCount() == 1)
90 state = std::move (newState);
91 else
92 state = newState.createCopy();
93
94 length = editLength.inSeconds();
95}
96
98{
99 return state.hasType (IDs::EDIT);
100}
101
103{
104 refreshFromState();
105 listeners.call (&Listener::editChanged, *this);
106}
107
108void EditSnapshot::addSubTracksRecursively (const juce::XmlElement& parent, int& audioTrackNameNumber)
109{
110 for (auto track : parent.getChildIterator())
111 {
112 juce::Identifier trackType (track->getTagName());
113
114 if (! TrackList::isTrack (trackType))
115 continue;
116
117 auto trackName = track->getStringAttribute (IDs::name);
118 trackIDs.add (EditItemID::fromXML (*track, IDs::id));
119
120 mutedTracks.setBit (numTracks, track->getBoolAttribute (IDs::mute, false));
121 soloedTracks.setBit (numTracks, track->getBoolAttribute (IDs::solo, false));
122 soloIsolatedTracks.setBit (numTracks, track->getBoolAttribute (IDs::soloIsolate, false));
123
124 if (trackType == IDs::TRACK || trackType == IDs::MARKERTRACK)
125 {
126 if (trackName.isEmpty())
127 trackName = TRANS("Track") + " " + juce::String (audioTrackNameNumber);
128
129 if (trackType == IDs::TRACK)
130 {
131 audioTracks.setBit (numTracks);
132 addEditClips (*track);
133 addClipSources (*track);
134 ++audioTrackNameNumber;
135 }
136 else if (trackType == IDs::MARKERTRACK)
137 {
138 addMarkers (*track);
139 }
140 }
141
142 trackNames.add (trackName);
143 ++numTracks;
144
145 addSubTracksRecursively (*track, audioTrackNameNumber);
146 }
147}
148
149void EditSnapshot::refreshFromXml (const juce::XmlElement& xml,
150 const juce::String& newName, double newLength)
151{
152 clear();
153 name = newName;
154 length = newLength;
155
156 // last significant change
157 auto changeHexString = xml.getStringAttribute (IDs::lastSignificantChange);
158 lastSaveTime = changeHexString.isEmpty() ? sourceFile.getLastModificationTime()
159 : juce::Time (changeHexString.getHexValue64());
160
161 // marks
162 if (auto viewState = xml.getChildByName (IDs::TRANSPORT))
163 {
164 auto loopRange = juce::Range<double>::between (viewState->getDoubleAttribute (IDs::loopPoint1),
165 viewState->getDoubleAttribute (IDs::loopPoint2));
166 markIn = loopRange.getStart();
167 markOut = loopRange.getEnd();
168
169 marksActive = markIn != markOut;
170 }
171
172 // tempo, time sig & pitch
173 if (auto tempoSeq = xml.getChildByName (IDs::TEMPOSEQUENCE))
174 {
175 if (auto tempoItem = tempoSeq->getChildByName (IDs::TEMPO))
176 tempo = tempoItem->getDoubleAttribute (IDs::bpm);
177
178 if (auto timeSigItem = tempoSeq->getChildByName (IDs::TIMESIG))
179 {
180 timeSigNumerator = timeSigItem->getIntAttribute (IDs::numerator);
181 timeSigDenominator = timeSigItem->getIntAttribute (IDs::denominator);
182 }
183 }
184
185 if (auto pitchSeq = xml.getChildByName (IDs::PITCHSEQUENCE))
186 if (auto pitchItem = pitchSeq->getChildByName (IDs::PITCH))
187 pitch = pitchItem->getIntAttribute (IDs::pitch);
188
189 // tracks
191
192 int audioTrackNameNumber = 1;
193 addSubTracksRecursively (xml, audioTrackNameNumber);
194 numAudioTracks = audioTracks.countNumberOfSetBits();
195}
196
197int EditSnapshot::audioToGlobalTrackIndex (int audioIndex) const
198{
199 int audioTrackIndex = 0;
200
201 for (int i = 0; i < numTracks; ++i)
202 if (isAudioTrack (i))
203 if (audioTrackIndex++ == audioIndex)
204 return i;
205
206 return -1;
207}
208
209//==============================================================================
211{
212 if (auto pi = engine.getProjectManager().getProjectItem (itemID))
213 refreshFromProjectItem (pi);
214}
215
216void EditSnapshot::refreshFromProjectItem (ProjectItem::Ptr pi)
217{
218 clear();
219
220 if (pi == nullptr)
221 return;
222
223 sourceFile = pi->getSourceFile();
224 auto newState = loadValueTree (sourceFile, true);
225
226 if (! newState.hasType (IDs::EDIT))
227 return;
228
229 name = pi->getName();
230 setState (newState, TimeDuration::fromSeconds (pi->getLength()));
231 refreshFromState();
232}
233
234void EditSnapshot::refreshFromState()
235{
236 auto editName = name;
237 auto editLength = length;
238 clear();
239
240 if (auto xml = state.createXml())
241 refreshFromXml (*xml, editName, editLength);
242}
243
244void EditSnapshot::clear()
245{
246 name = {};
247 numTracks = 0;
248 numAudioTracks = 0;
249 trackNames.clearQuick();
250 audioTracks.clear();
251 trackIDs.clear();
252 editClipIDs.clear();
253 clipSourceIDs.clear();
254 length = 0.0;
255 markIn = 0.0;
256 markOut = 0.0;
257 tempo = 0.0;
258
259 marksActive = false;
260 timeSigNumerator = 4;
261 timeSigDenominator = 4;
262 pitch = 60;
263 markers.clear();
264}
265
266void EditSnapshot::addEditClips (const juce::XmlElement& track)
267{
268 for (auto clip : track.getChildIterator())
269 if (clip->hasTagName (IDs::EDITCLIP))
270 editClipIDs.add (ProjectItemID (clip->getStringAttribute ("source")));
271}
272
273void EditSnapshot::addClipSources (const juce::XmlElement& track)
274{
275 for (auto clip : track.getChildIterator())
276 {
277 auto sourceID = clip->getStringAttribute ("source");
278
279 if (sourceID.isNotEmpty())
280 clipSourceIDs.add (ProjectItemID (sourceID));
281 }
282}
283
284void EditSnapshot::addMarkers (const juce::XmlElement& track)
285{
286 for (auto clip : track.getChildIterator())
287 {
288 Marker m;
289 m.name = clip->getStringAttribute ("name", TRANS("unnamed"));
290 m.colour = juce::Colour::fromString (clip->getStringAttribute ("colour", TRANS("unnamed")));
291 auto start = TimePosition::fromSeconds (clip->getDoubleAttribute ("start", 0.0));
292 auto len = TimeDuration::fromSeconds (clip->getDoubleAttribute ("length", 0.0));
293 m.time = { start, start + len };
294
295 if (len > 0s)
296 markers.add (m);
297 }
298}
299
300
301static void addNestedEditObjects (EditSnapshot& baseEdit, juce::ReferenceCountedArray<EditSnapshot>& edits)
302{
303 edits.addIfNotAlreadyThere (&baseEdit);
304
305 for (auto itemID : baseEdit.getEditClips())
306 if (auto ptr = EditSnapshot::getEditSnapshot (baseEdit.engine, itemID))
307 addNestedEditObjects (*ptr, edits);
308}
309
311{
314 addNestedEditObjects (*this, result);
315 return result;
316}
317
318}} // namespace tracktion { inline namespace engine
BigInteger & clear() noexcept
int countNumberOfSetBits() const noexcept
BigInteger & setBit(int bitNumber)
static Colour fromString(StringRef encodedColourString)
Time getLastModificationTime() const
static constexpr Range between(const ValueType position1, const ValueType position2) noexcept
bool addIfNotAlreadyThere(ObjectClass *newObject)
void add(String stringToAdd)
void ensureStorageAllocated(int minNumElements)
std::unique_ptr< XmlElement > createXml() const
bool hasType(const Identifier &typeName) const noexcept
int getReferenceCount() const noexcept
ValueTree createCopy() const
int getNumChildElements() const noexcept
XmlElement * getChildByName(StringRef tagNameToLookFor) const noexcept
const String & getStringAttribute(StringRef attributeName) const noexcept
Holds a snapshot of an Edit file of the last time it was saved.
void setState(juce::ValueTree, TimeDuration editLength)
Sets the Edit XML that the XmlEdit should refer to.
void refreshCacheAndNotifyListeners()
Refreshes the cached properties and calls any listeners.
bool isValid() const
Returns true if the current source is a valid Edit.
void refreshFromProjectManager()
Looks in the ProjectManager for the relevant ProjectItem and updates it's state to reflect this.
juce::ReferenceCountedArray< EditSnapshot > getNestedEditObjects()
deals only with snapshots so it's relatively fast.
The Engine is the central class for all tracktion sessions.
ProjectManager & getProjectManager() const
Returns the ProjectManager instance.
An ID representing one of the items in a Project.
ProjectItem::Ptr getProjectItem(ProjectItemID)
tries to find the project that contains an id, and open it as a ProjectItem.
#define TRANS(stringLiteral)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
Represents a duration in real-life time.
constexpr double inSeconds() const
Returns the TimeDuration as a number of seconds.
static bool isTrack(const juce::ValueTree &) noexcept
Returns true if the given ValeTree is for a known Track type.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.