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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_OldEditConversion.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 tracktion { inline namespace engine
12{
13
14//==============================================================================
21{
22 static juce::ValueTree convert (const juce::ValueTree& v)
23 {
24 jassert (v.isValid());
25
26 if (auto xml = v.createXml())
27 {
28 convert (*xml);
29 return juce::ValueTree::fromXml (*xml);
30 }
31
32 return v;
33 }
34
35 static void convert (juce::XmlElement& editXML)
36 {
38
39 updateV2TempoData (editXML);
40 convertV2Markers (editXML);
41 convertVideo (editXML);
42 convertOldView (editXML);
43 convertV9PitchSequence (editXML);
44 convertV9Markers (editXML);
45 convertMidiVersion (editXML);
46 convertMpeVersion (editXML);
47 convertPluginsAndClips (editXML);
48 fixTrackTags (editXML);
49 convertFolderTracks (editXML);
50 convertLegacyLFOs (editXML);
51 convertLegacyIDsIfNeeded (editXML);
52 }
53
54private:
55 //==============================================================================
56 static void renameAttribute (juce::XmlElement& e, const juce::Identifier& oldAtt, const juce::Identifier& newAtt)
57 {
58 if (e.hasAttribute (oldAtt))
59 {
60 e.setAttribute (newAtt, e.getStringAttribute (oldAtt));
61 e.removeAttribute (oldAtt);
62 }
63 }
64
65 static void moveAttribute (juce::XmlElement& oldXML, juce::XmlElement& newXML, const juce::Identifier& name)
66 {
67 if (oldXML.hasAttribute (name))
68 {
69 newXML.setAttribute (name, oldXML.getStringAttribute (name));
70 oldXML.removeAttribute (name);
71 }
72 }
73
74 static void moveAttribute (juce::XmlElement& oldXML, juce::XmlElement& newXML, const juce::Identifier& oldName, const juce::Identifier& newName)
75 {
76 if (oldXML.hasAttribute (oldName))
77 {
78 newXML.setAttribute (newName, oldXML.getStringAttribute (oldName));
79 oldXML.removeAttribute (oldName);
80 }
81 }
82 static void convertSequenceMidiVersion (juce::XmlElement& xml, double timeToBeatFactor)
83 {
84 for (auto seq : xml.getChildWithTagNameIterator ("MIDISEQUENCE"))
85 {
86 if (seq->getIntAttribute (IDs::ver, 0) < 1)
87 {
88 for (auto e : seq->getChildIterator())
89 {
90 if (e->hasTagName (IDs::NOTE))
91 {
92 const double startBeat = timeToBeatFactor * e->getDoubleAttribute (IDs::t);
93 const double lengthInBeats = juce::jmax (0.0, timeToBeatFactor * e->getDoubleAttribute (IDs::l));
94 e->removeAttribute (IDs::t);
95 e->setAttribute (IDs::b, startBeat);
96 e->setAttribute (IDs::l, lengthInBeats);
97 }
98 else if (e->hasTagName (IDs::CONTROL))
99 {
100 const double startBeat = timeToBeatFactor * e->getDoubleAttribute (IDs::time);
101 e->removeAttribute (IDs::time);
102 e->setAttribute (IDs::b, startBeat);
103 }
104 else if (e->hasTagName (IDs::SYSEX))
105 {
106 const double startBeat = timeToBeatFactor * e->getDoubleAttribute (IDs::time);
107 e->setAttribute (IDs::time, startBeat);
108 }
109 }
110 }
111 }
112
113 for (auto e : xml.getChildIterator())
114 if (! e->hasTagName ("MIDISEQUENCE"))
115 convertSequenceMidiVersion (*e, timeToBeatFactor);
116
117 // Convert note automation to midi expression, and tag the note automation as
118 // converted so it doesn't happen again.
119 for (auto seq : xml.getChildWithTagNameIterator ("MIDISEQUENCE"))
120 {
121 for (auto note : seq->getChildWithTagNameIterator (IDs::NOTE))
122 {
123 if (auto na = note->getChildByName (IDs::NA))
124 {
125 if (auto* naseq = na->getChildByName ("MIDISEQUENCE"))
126 {
127 // If this sequence has already been converted, don't convert again
128 if (naseq->getBoolAttribute (IDs::converted, false))
129 continue;
130
131 naseq->setAttribute (IDs::converted, true);
132
133 for (auto control : naseq->getChildWithTagNameIterator (IDs::CONTROL))
134 {
135 // Timbre
136 if (control->getIntAttribute (IDs::type) == 71)
137 {
138 auto expression = new juce::XmlElement (IDs::TIMBRE);
139
140 expression->setAttribute (IDs::b, control->getDoubleAttribute (IDs::b));
141 expression->setAttribute (IDs::v, control->getIntAttribute (IDs::val) / 16383.0);
142
143 note->addChildElement (expression);
144 }
145 // Pitch wheel
146 if (control->getIntAttribute (IDs::type) == 4101)
147 {
148 auto range = juce::NormalisableRange<float> (-48.0f, 48.0f);
149
150 auto expression = new juce::XmlElement (IDs::PITCHBEND);
151 expression->setAttribute (IDs::b, control->getDoubleAttribute (IDs::b));
152 expression->setAttribute (IDs::v, range.convertFrom0to1 (control->getIntAttribute (IDs::val) / 16383.0f));
153 note->addChildElement (expression);
154 }
155 // Channel Pressure
156 if (control->getIntAttribute (IDs::type) == 4103)
157 {
158 auto expression = new juce::XmlElement (IDs::PRESSURE);
159 expression->setAttribute (IDs::b, control->getDoubleAttribute (IDs::b));
160 expression->setAttribute (IDs::v, control->getIntAttribute (IDs::val) / 16383.0f);
161 note->addChildElement (expression);
162 }
163
164 }
165 }
166 }
167 }
168 }
169 }
170
171 static void convertTrackMidiVersion (juce::XmlElement& track, double editTempo)
172 {
173 for (auto clip : track.getChildWithTagNameIterator ("CLIP"))
174 {
175 const double clipSpeed = clip->getDoubleAttribute (IDs::speed, 1.0);
176 const double timeToBeatFactor = (editTempo / 60.0) / clipSpeed;
177 convertSequenceMidiVersion (*clip, timeToBeatFactor);
178 }
179 }
180
181 static void convertMidiVersion (juce::XmlElement& editXml)
182 {
184
185 if (auto t = editXml.getChildByName (IDs::TEMPOSEQUENCE))
186 {
187 auto editTempo = t->getDoubleAttribute (IDs::bpm);
188
189 for (auto e : editXml.getChildWithTagNameIterator (IDs::TRACK))
190 convertTrackMidiVersion (*e, editTempo);
191 }
192 }
193
194 static void convertMpeVersion (juce::XmlElement& xmlToSearchRecursively)
195 {
196 // We recursively search for CLIP elements, because NOISE's clips
197 // live in SLOTs, whose Identifier isn't available here.
198 for (auto element : xmlToSearchRecursively.getChildIterator())
199 {
200 if (element->hasTagName ("CLIP"))
201 {
202 if (auto* sequence = element->getChildByName ("MIDISEQUENCE"))
203 {
204 for (auto note : sequence->getChildWithTagNameIterator (IDs::NOTE))
205 {
206 if (note->getFirstChildElement() != nullptr
207 && ! note->hasAttribute (IDs::bend))
208 {
209 if (auto firstTimbre = note->getChildByName (IDs::TIMBRE))
210 note->setAttribute (IDs::timb, firstTimbre->getDoubleAttribute ("v"));
211
212 if (auto firstPressure = note->getChildByName (IDs::PRESSURE))
213 note->setAttribute (IDs::pres, firstPressure->getDoubleAttribute ("v"));
214
215 if (auto firstPitchbend = note->getChildByName (IDs::PITCHBEND))
216 note->setAttribute (IDs::bend, firstPitchbend->getDoubleAttribute ("v"));
217 }
218 }
219 }
220 }
221 else
222 {
223 convertMpeVersion (*element);
224 }
225 }
226 }
227
228 static void updateOldStepClip (juce::XmlElement& xml)
229 {
230 auto patterns = new juce::XmlElement (IDs::PATTERNS);
231 auto channels = new juce::XmlElement (IDs::CHANNELS);
232
233 for (auto e : xml.getChildWithTagNameIterator (IDs::PATTERN))
234 patterns->addChildElement (new juce::XmlElement (*e));
235
236 for (auto e : xml.getChildWithTagNameIterator (IDs::CHANNEL))
237 channels->addChildElement (new juce::XmlElement (*e));
238
240
241 xml.addChildElement (channels);
242 xml.addChildElement (patterns);
243
244 for (auto e : patterns->getChildIterator())
245 {
246 for (auto chan : e->getChildWithTagNameIterator (IDs::CHANNEL))
247 {
248 chan->setAttribute (IDs::pattern, chan->getAllSubText().trim());
249 chan->deleteAllChildElements();
250 chan->deleteAllTextElements();
251 }
252 }
253
254 for (auto e : channels->getChildIterator())
255 {
256 renameAttribute (*e, IDs::noteNumber, IDs::note);
257 renameAttribute (*e, IDs::noteValue, IDs::velocity);
258 }
259 }
260
261 static void convertPluginsAndClips (juce::XmlElement& xml)
262 {
263 for (auto e : xml.getChildIterator())
264 {
265 convertPluginsAndClips (*e);
266
267 if (e->hasTagName (IDs::PLUGIN))
268 {
269 if (auto* vstData = e->getChildByName (IDs::VSTDATA))
270 {
271 e->setAttribute (IDs::state, vstData->getAllSubText().trim());
272 e->removeChildElement (vstData, true);
273 }
274 else if (e->getStringAttribute (IDs::type) == AuxSendPlugin::xmlTypeName)
275 {
276 if (e->hasAttribute (IDs::gain))
277 {
278 e->setAttribute (IDs::auxSendSliderPos, decibelsToVolumeFaderPosition ((float) e->getDoubleAttribute (IDs::gain, 0.0f)));
279 e->removeAttribute (IDs::gain);
280 }
281 }
282 }
283 else if (e->hasTagName ("CLIP"))
284 {
285 if (e->getChildByName (IDs::CHANNEL) != nullptr || e->getChildByName (IDs::PATTERN) != nullptr)
286 updateOldStepClip (*e);
287 }
288 }
289 }
290
291 static void updateV2TempoData (juce::XmlElement& xml)
292 {
294
295 for (auto e : xml.getChildWithTagNameIterator (IDs::TEMPO))
296 oldTempos.add (e);
297
298 if (oldTempos.size() > 0)
299 {
300 auto tempoSequence = xml.getChildByName (IDs::TEMPOSEQUENCE);
301
302 if (tempoSequence == nullptr)
303 tempoSequence = xml.createNewChildElement (IDs::TEMPOSEQUENCE);
304
305 for (auto* e : oldTempos)
306 {
307 juce::XmlElement tempo (IDs::TEMPO);
308 tempo.setAttribute (IDs::startBeat, e->getIntAttribute (IDs::startBeat));
309 tempo.setAttribute (IDs::bpm, e->getDoubleAttribute (IDs::bpm));
310 tempo.setAttribute (IDs::curve, e->getIntAttribute (IDs::ramped) ? 0.0f : 1.0f);
311
312 juce::XmlElement timesig (IDs::TIMESIG);
313 timesig.setAttribute (IDs::startBeat, e->getIntAttribute (IDs::startBeat));
314 timesig.setAttribute (IDs::numerator, e->getIntAttribute (IDs::numerator));
315 timesig.setAttribute (IDs::denominator, e->getIntAttribute (IDs::denominator));
316 timesig.setAttribute (IDs::triplets, e->getIntAttribute (IDs::triplets));
317
318 tempoSequence->addChildElement (new juce::XmlElement (tempo));
319 tempoSequence->addChildElement (new juce::XmlElement (timesig));
320
321 xml.removeChildElement (e, true);
322 }
323 }
324 else if (auto tempoSequence = xml.getChildByName (IDs::TEMPOSEQUENCE))
325 {
326 bool foundOld = false;
327
328 for (auto e : tempoSequence->getChildWithTagNameIterator (IDs::TEMPO))
329 {
330 if (! (e->hasAttribute (IDs::numerator) || e->hasAttribute (IDs::denominator) || e->hasAttribute (IDs::triplets)))
331 continue;
332
333 oldTempos.add (e);
334 foundOld = true;
335 }
336
337 if (foundOld)
338 {
339 for (auto e : oldTempos)
340 {
341 juce::XmlElement timesig (IDs::TIMESIG);
342 timesig.setAttribute (IDs::startBeat, e->getIntAttribute (IDs::startBeat));
343 timesig.setAttribute (IDs::numerator, e->getIntAttribute (IDs::numerator));
344 timesig.setAttribute (IDs::denominator, e->getIntAttribute (IDs::denominator));
345 timesig.setAttribute (IDs::triplets, e->getIntAttribute (IDs::triplets));
346
347 e->setAttribute (IDs::curve, e->getIntAttribute (IDs::ramped) ? 0.0f : 1.0f);
348
349 e->removeAttribute (IDs::numerator);
350 e->removeAttribute (IDs::denominator);
351 e->removeAttribute (IDs::triplets);
352 e->removeAttribute (IDs::ramped);
353
354 tempoSequence->addChildElement (new juce::XmlElement (timesig));
355 }
356 }
357 }
358 }
359
360 static void convertV2Markers (juce::XmlElement& xml)
361 {
362 if (auto viewStateXML = xml.getChildByName ("VIEWSTATE"))
363 {
364 juce::Array<int> numbers;
366
367 for (auto e : viewStateXML->getChildWithTagNameIterator (IDs::MARKER))
368 {
369 numbers.add (e->getIntAttribute (IDs::number));
370 times.add (e->getDoubleAttribute (IDs::time));
371 }
372
373 if (numbers.size() > 0)
374 {
375 auto markerTrack = xml.getChildByName (IDs::MARKERTRACK);
376
377 if (markerTrack == nullptr)
378 {
379 markerTrack = xml.createNewChildElement (IDs::MARKERTRACK);
380
381 markerTrack->setAttribute (IDs::name, TRANS("Marker"));
382 markerTrack->setAttribute (IDs::trackType, (int) 0 /*Track::Type::markerCombined*/);
383 }
384
385 for (int i = 0; i < numbers.size(); ++i)
386 {
387 auto clip = markerTrack->createNewChildElement ("CLIP");
388 clip->setAttribute (IDs::name, "Marker");
389 clip->setAttribute (IDs::markerID, numbers[i]);
390 clip->setAttribute (IDs::type, "absoluteMarker");
391 clip->setAttribute (IDs::start, times[i]);
392 clip->setAttribute (IDs::length, 2.0);
393 }
394 }
395 }
396 }
397
398 static void convertVideo (juce::XmlElement& xml)
399 {
400 if (xml.getChildByName (IDs::VIDEO) != nullptr)
401 return;
402
403 if (auto viewStateXML = xml.getChildByName ("VIEWSTATE"))
404 {
405 auto videoXML = xml.createNewChildElement (IDs::VIDEO);
406
407 moveAttribute (*viewStateXML, *videoXML, "videoFile");
408 moveAttribute (*viewStateXML, *videoXML, IDs::videoOffset);
409 moveAttribute (*viewStateXML, *videoXML, IDs::videoMuted);
410 moveAttribute (*viewStateXML, *videoXML, IDs::videoSource);
411 }
412 }
413
414 static void convertOldView (juce::XmlElement& xml)
415 {
416 if (auto viewStateXML = xml.getChildByName ("VIEWSTATE"))
417 {
418 auto transportXML = xml.getChildByName (IDs::TRANSPORT);
419
420 if (transportXML == nullptr)
421 transportXML = xml.createNewChildElement (IDs::TRANSPORT);
422
423 moveAttribute (*viewStateXML, *transportXML, IDs::markIn, IDs::loopPoint1);
424 moveAttribute (*viewStateXML, *transportXML, IDs::markOut, IDs::loopPoint2);
425
426 moveAttribute (*viewStateXML, *transportXML, IDs::loopPlayback, IDs::looping);
427 moveAttribute (*viewStateXML, *transportXML, IDs::automationRead);
428 moveAttribute (*viewStateXML, *transportXML, IDs::recordPunchInOut);
429 moveAttribute (*viewStateXML, *transportXML, IDs::endToEnd);
430
431 moveAttribute (*viewStateXML, *transportXML, IDs::midiTimecodeOffset);
432 moveAttribute (*viewStateXML, *transportXML, IDs::midiTimecodeEnabled);
433 moveAttribute (*viewStateXML, *transportXML, IDs::midiTimecodeIgnoringHours);
434 moveAttribute (*viewStateXML, *transportXML, IDs::midiTimecodeSourceDevice);
435 moveAttribute (*viewStateXML, *transportXML, IDs::midiMachineControlSourceDevice);
436 moveAttribute (*viewStateXML, *transportXML, IDs::midiMachineControlDestDevice);
437 }
438 }
439
440 static void convertV9PitchSequence (juce::XmlElement& xml)
441 {
442 if (auto pitchSequence = xml.getChildByName (IDs::PITCHSEQUENCE))
443 for (auto e : pitchSequence->getChildWithTagNameIterator (IDs::PITCH))
444 renameAttribute (*e, IDs::start, IDs::startBeat);
445 }
446
447 static void convertV9Markers (juce::XmlElement& xml)
448 {
449 if (auto firstMarkerTrack = xml.getChildByName (IDs::MARKERTRACK))
450 {
451 juce::Array<juce::XmlElement*> tracksToRemove;
452
453 for (auto markerTrack : xml.getChildWithTagNameIterator (IDs::MARKERTRACK))
454 {
455 if (firstMarkerTrack == markerTrack)
456 continue;
457
458 for (auto clip : markerTrack->getChildWithTagNameIterator ("CLIP"))
459 firstMarkerTrack->addChildElement (new juce::XmlElement (*clip));
460
461 tracksToRemove.add (markerTrack);
462 }
463
464 for (auto markerTrack : tracksToRemove)
465 xml.removeChildElement (markerTrack, true);
466
467 firstMarkerTrack->removeAttribute (IDs::trackType);
468 }
469 }
470
471 static juce::XmlElement* getParentWithId (juce::Array<juce::XmlElement*>& tracks, EditItemID parentID)
472 {
473 for (auto e : tracks)
474 if (EditItemID::fromXML (*e, "mediaId") == parentID)
475 return e;
476
477 return {};
478 }
479
480 static void addTracks (juce::Array<juce::XmlElement*>& tracks, juce::XmlElement& xml)
481 {
482 for (auto e : xml.getChildIterator())
483 {
485 {
486 tracks.add (e);
487 addTracks (tracks, *e);
488 }
489 }
490 }
491
492 static void fixTrackTags (juce::XmlElement& xml)
493 {
495 addTracks (tracks, xml);
496
497 for (auto e : tracks)
498 {
499 if (! e->hasAttribute (IDs::tags))
500 continue;
501
502 auto tagsString = e->getStringAttribute (IDs::tags);
503
504 if (tagsString.contains (","))
505 tagsString = juce::StringArray::fromTokens (tagsString, ",", "\"").joinIntoString ("|");
506
507 auto tags = juce::StringArray::fromTokens (tagsString, "|", "\"");
508
509 for (auto& tag : tags)
510 tag = tag.trimCharactersAtStart ("_").trimCharactersAtEnd ("_");
511
512 tags.trim();
513 tags.removeEmptyStrings();
514 e->setAttribute (IDs::tags, tags.joinIntoString ("|"));
515 }
516 }
517
518 static void convertFolderTracks (juce::XmlElement& xml)
519 {
521 addTracks (tracks, xml);
522
523 for (auto e : tracks)
524 {
525 auto parentID = EditItemID::fromXML (*e, "parentId");
526 e->removeAttribute ("parentId");
527
528 if (! parentID.isValid())
529 continue;
530
531 if (auto p = getParentWithId (tracks, parentID))
532 {
533 if (auto p2 = xml.findParentElementOf (e))
534 p2->removeChildElement (e, false);
535 else
536 { jassertfalse; }
537
538 p->addChildElement (e);
539 }
540 else
541 {
542 jassertfalse; // Missing folder track?
543 }
544 }
545 }
546
547 static void addNodeRecursively (juce::Array<juce::XmlElement*>& plugins,
548 juce::XmlElement& xml, const juce::Identifier& tagName)
549 {
550 if (xml.hasTagName (tagName))
551 plugins.add (&xml);
552
553 for (auto e : xml.getChildIterator())
554 addNodeRecursively (plugins, *e, tagName);
555 }
556
557 static void convertLegacyLFOs (juce::XmlElement& xml)
558 {
559 juce::Array<juce::XmlElement*> tracks, plugins, lfosToDelete, automationSourcesToDelete;
560 addTracks (tracks, xml);
561 addNodeRecursively (plugins, xml, IDs::PLUGIN);
562
563 // This will be done before the FILTER -> PLUGIN conversion so we need to account for both
564 addNodeRecursively (plugins, xml, "FILTER");
565
566 // - Convert LFOS to MODIFIERS
567 // Find LFOS node, rename to MODIFIERS
568 // For each LFO child change:
569 // depth = intensity / 100
570 // phase /= 360
571 // syncType = transport
572 // rateType = (sync 0 == time, sync 1 == beat)
573 // if sync == time, rate = frequency
574 // if sync == beat, rate = (whole = 1, half = 0.5, quarter = 0.25, eighth = 0.125, sixteenth = 0.0625)
575
576 for (auto t : tracks)
577 {
578 if (auto lfos = t->getChildByName (IDs::LFOS))
579 {
580 lfosToDelete.add (lfos);
581
582 auto modifiers = t->getChildByName (IDs::MODIFIERS);
583
584 if (modifiers == nullptr)
585 modifiers = t->createNewChildElement (IDs::MODIFIERS);
586
587 for (auto lfo : lfos->getChildWithTagNameIterator (IDs::LFO))
588 {
589 auto newLFO = new juce::XmlElement (*lfo);
590 modifiers->addChildElement (newLFO);
591 newLFO->setAttribute (IDs::syncType, 1);
592 newLFO->setAttribute (IDs::depth, lfo->getDoubleAttribute (IDs::intensity, 50.0) / 100.0);
593 newLFO->setAttribute (IDs::phase, lfo->getDoubleAttribute (IDs::phase) / 360.0);
594
595 if (lfo->getIntAttribute (IDs::sync, 1) == 0)
596 {
597 // time in Hz
598 newLFO->setAttribute (IDs::rateType, 0);
599 newLFO->setAttribute (IDs::rate, lfo->getDoubleAttribute (IDs::frequency, 1.0));
600 }
601 else
602 {
603 // beat (whole = 1, half = 0.5, quarter = 0.25, eighth = 0.125, sixteenth = 0.0625)
604 newLFO->setAttribute (IDs::rateType, lfo->getIntAttribute (IDs::beat, 0) + 1.0);
605 newLFO->setAttribute (IDs::rate, 1.0);
606 }
607 }
608 }
609 }
610
611 // - Convert AUTOMATIONSOURCE to LFO
612 // find AUTOMATIONSOURCE with lfoSource
613 // rename to LFO
614 // get paramName, store to paramID
615 // get lfoSource, store to source
616 // set value to 1
617 // add to (might have to create) sibling MODIFIERASSIGNMENTS
618
619 for (auto f : plugins)
620 {
621 auto modifierAssignments = f->getChildByName (IDs::MODIFIERASSIGNMENTS);
622
623 if (modifierAssignments == nullptr)
624 modifierAssignments = f->createNewChildElement (IDs::MODIFIERASSIGNMENTS);
625
626 for (auto e : f->getChildWithTagNameIterator (IDs::AUTOMATIONSOURCE))
627 {
628 if (e->hasAttribute (IDs::lfoSource))
629 {
630 auto lfo = modifierAssignments->createNewChildElement (IDs::LFO);
631 lfo->setAttribute (IDs::source, e->getStringAttribute (IDs::lfoSource));
632 lfo->setAttribute (IDs::paramID, e->getStringAttribute (IDs::paramName));
633 lfo->setAttribute (IDs::value, 1.0);
634
635 automationSourcesToDelete.add (e);
636 }
637 }
638 }
639
640 for (auto lfos : lfosToDelete)
641 if (auto p = xml.findParentElementOf (lfos))
642 p->removeChildElement (lfos, true);
643
644 for (auto automationSource : automationSourcesToDelete)
645 if (auto p = xml.findParentElementOf (automationSource))
646 p->removeChildElement (automationSource, true);
647 }
648
649 static void convertLegacyIDs (juce::XmlElement& xml)
650 {
651 renameLegacyIDs (xml);
652
653 uint64_t nextID = 1001;
654 EditItemID::remapIDs (xml, [&] { return EditItemID::fromRawID (nextID++); });
655
656 recurseDoingLegacyConversions (xml);
657 }
658
659 static void convertLegacyIDsIfNeeded (juce::XmlElement& xml)
660 {
661 if (xml.hasTagName (IDs::EDIT))
662 {
663 if (xml.getStringAttribute (IDs::projectID).isEmpty())
664 {
665 renameAttribute (xml, "mediaId", IDs::projectID);
666 moveXMLAttributeToStart (xml, IDs::projectID);
667 moveXMLAttributeToStart (xml, IDs::appVersion);
668
669 convertLegacyIDs (xml);
670 }
671
672 convertLegacyTimecodes (xml);
673 convertLegacyInputTargets (xml);
674 }
675 else
676 {
677 convertLegacyIDs (xml);
678 }
679 }
680
681 static void tidyIDListDelimiters (juce::XmlElement& xml, const juce::Identifier& att)
682 {
683 if (xml.hasAttribute (att))
684 xml.setAttribute (att, xml.getStringAttribute (att).replace ("|", ","));
685 }
686
687 static void renameLegacyIDs (juce::XmlElement& xml)
688 {
689 for (auto e : xml.getChildIterator())
690 renameLegacyIDs (*e);
691
692 if (xml.hasTagName (IDs::TAKE) || xml.hasTagName (IDs::SOUND))
693 {
694 renameAttribute (xml, "mediaId", IDs::source);
695 }
696 else if (ModifierList::isModifier (xml.getTagName())
698 || xml.hasTagName ("CLIP")
699 || xml.hasTagName ("PRESETMETADATA")
700 || xml.hasTagName (IDs::MACROPARAMETERS)
701 || xml.hasTagName (IDs::MACROPARAMETER))
702 {
703 jassert (! (xml.hasAttribute ("mediaId") && xml.hasAttribute (IDs::id)));
704 renameAttribute (xml, "mediaId", IDs::id);
705 renameAttribute (xml, "markerId", IDs::markerID);
706 renameAttribute (xml, "groupId", IDs::groupID);
707 renameAttribute (xml, "linkId", IDs::linkID);
708 renameAttribute (xml, "currentAutoParamFilter", IDs::currentAutoParamPluginID);
709
710 if (xml.hasAttribute ("maxNumChannels"))
711 {
712 if (! (xml.hasAttribute (IDs::mpeMode) || xml.getIntAttribute ("maxNumChannels") == 0))
713 xml.setAttribute (IDs::mpeMode, 1);
714
715 xml.removeAttribute ("maxNumChannels");
716 }
717
718 tidyIDListDelimiters (xml, IDs::ghostTracks);
719 }
720 else if (xml.hasTagName (IDs::CONTROLLERMAPPINGS))
721 {
722 renameAttribute (xml, "filterid", IDs::pluginID);
723 }
724 else if (xml.hasTagName ("VIEWSTATE"))
725 {
726 renameAttribute (xml, "videoFileId", IDs::videoSource);
727 renameAttribute (xml, "filterAreaWidth", IDs::pluginAreaWidth);
728 renameAttribute (xml, "filtersVisible", IDs::pluginsVisible);
729 tidyIDListDelimiters (xml, IDs::hiddenClips);
730 tidyIDListDelimiters (xml, IDs::lockedClips);
731 }
732 else if (xml.hasTagName ("DEVICESEX"))
733 {
734 xml.setTagName (IDs::INPUTDEVICES);
735 }
736 else if (xml.hasTagName ("INPUTDEVICE"))
737 {
738 auto name = xml.getStringAttribute ("name");
739
740 if (name.endsWith (" A") || name.endsWith (" M"))
741 {
742 xml.setAttribute (IDs::sourceTrack, name.upToLastOccurrenceOf (" ", false, false));
743 xml.setAttribute (IDs::type, name.endsWith (" M") ? "MIDI" : "audio");
744 xml.removeAttribute ("name");
745 }
746 }
747 else if (xml.hasTagName (IDs::INPUTDEVICEDESTINATION))
748 {
749 renameAttribute (xml, "targetTrack", IDs::targetID);
750 }
751 else if (xml.hasTagName ("RENDER"))
752 {
753 renameAttribute (xml, "renderFilter", IDs::renderPlugins);
754 }
755 else
756 {
757 jassert (! xml.hasAttribute ("mediaId")); // missed a type?
758 }
759
760 moveXMLAttributeToStart (xml, IDs::id);
761 }
762
763 static void convertLegacyFilterTagsToPlugin (juce::XmlElement& xml)
764 {
765 if (xml.hasTagName ("FILTER")) xml.setTagName (IDs::PLUGIN);
766 if (xml.hasTagName ("FILTERINSTANCE")) xml.setTagName (IDs::PLUGININSTANCE);
767 if (xml.hasTagName ("FILTERCONNECTION")) xml.setTagName (IDs::CONNECTION);
768 if (xml.hasTagName ("MASTERFILTERS")) xml.setTagName (IDs::MASTERPLUGINS);
769 if (xml.hasTagName ("RACKFILTER")) xml.setTagName (IDs::RACK);
770 if (xml.hasTagName ("RACKFILTERS")) xml.setTagName (IDs::RACKS);
771 }
772
773 static void convertLegacyRackConnectionIDs (juce::XmlElement& xml)
774 {
775 if (! xml.hasTagName (IDs::CONNECTION))
776 return;
777
778 if (xml.getStringAttribute (IDs::src) == "0/0") xml.setAttribute (IDs::src, "0");
779 if (xml.getStringAttribute (IDs::dst) == "0/0") xml.setAttribute (IDs::dst, "0");
780 }
781
782 static void convertLegacyClipFormat (juce::XmlElement& xml)
783 {
784 if (xml.hasTagName ("CLIP"))
785 {
786 auto type = TrackItem::stringToType (xml.getStringAttribute ("type"));
788 xml.removeAttribute ("type");
789 }
790 }
791
792 static void setNewTimecode (juce::XmlElement& xml, TimecodeType type)
793 {
794 xml.setAttribute (IDs::timecodeFormat, juce::VariantConverter<TimecodeDisplayFormat>::toVar (type).toString());
795 }
796
797 static void convertLegacyTimecodes (juce::XmlElement& xml)
798 {
799 auto timeFormat = xml.getStringAttribute (IDs::timecodeFormat);
800 auto fps = xml.getIntAttribute (IDs::fps);
801 xml.removeAttribute (IDs::fps);
802
803 if (timeFormat == "0" || timeFormat.contains ("milli")) return setNewTimecode (xml, TimecodeType::millisecs);
804 if (timeFormat == "1" || timeFormat.contains ("beat")) return setNewTimecode (xml, TimecodeType::barsBeats);
805
806 if (timeFormat == "2") return setNewTimecode (xml, TimecodeType::fps24);
807 if (timeFormat == "3") return setNewTimecode (xml, TimecodeType::fps25);
808 if (timeFormat == "4") return setNewTimecode (xml, TimecodeType::fps30);
809
810 if (fps != 0 && timeFormat == "frames")
811 {
812 if (fps == 24) return setNewTimecode (xml, TimecodeType::fps24);
813 if (fps == 25) return setNewTimecode (xml, TimecodeType::fps25);
814 if (fps == 30) return setNewTimecode (xml, TimecodeType::fps30);
815 }
816 }
817
818 static void convertLegacyMidiSequences (juce::XmlElement& xml)
819 {
820 if (xml.hasTagName ("MIDISEQUENCE"))
821 {
822 xml.setTagName (IDs::SEQUENCE);
823 xml.removeAttribute ("ver");
824 }
825 else if (xml.hasTagName ("NOTE"))
826 {
827 renameAttribute (xml, "noteOffVelocity", IDs::lift);
828
829 if (xml.getDoubleAttribute ("initialTimbre") == MidiList::defaultInitialTimbreValue)
830 xml.removeAttribute ("initialTimbre");
831 else
832 renameAttribute (xml, "initialTimbre", IDs::timb);
833
834 if (xml.getDoubleAttribute ("initialPressure") == MidiList::defaultInitialPressureValue)
835 xml.removeAttribute ("initialPressure");
836 else
837 renameAttribute (xml, "initialPressure", IDs::pres);
838
839 if (xml.getDoubleAttribute ("initialPitchbend") == MidiList::defaultInitialPitchBendValue)
840 xml.removeAttribute ("initialPitchbend");
841 else
842 renameAttribute (xml, "initialPitchbend", IDs::bend);
843 }
844 }
845
846 static void convertLegacyInputTargets (juce::XmlElement& xml)
847 {
848 for (auto e : xml.getChildIterator())
849 convertLegacyInputTargets (*e);
850
851 if (xml.hasTagName (IDs::INPUTDEVICEDESTINATION))
852 renameAttribute (xml, "targetTrack", IDs::targetID);
853 }
854
855 static void recurseDoingLegacyConversions (juce::XmlElement& xml)
856 {
857 for (auto e : xml.getChildIterator())
858 recurseDoingLegacyConversions (*e);
859
860 convertLegacyFilterTagsToPlugin (xml);
861 convertLegacyRackConnectionIDs (xml);
862 convertLegacyClipFormat (xml);
863 convertLegacyMidiSequences (xml);
864 }
865};
866
867juce::ValueTree updateLegacyEdit (const juce::ValueTree& v) { return OldEditConversion::convert (v); }
868void updateLegacyEdit (juce::XmlElement& editXML) { OldEditConversion::convert (editXML); }
869
870}} // namespace tracktion { inline namespace engine
int size() const noexcept
void add(const ElementType &newElement)
bool isValid() const noexcept
String joinIntoString(StringRef separatorString, int startIndex=0, int numberOfElements=-1) const
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
bool isEmpty() const noexcept
String replace(StringRef stringToReplace, StringRef stringToInsertInstead, bool ignoreCase=false) const
void setTagName(StringRef newTagName)
XmlElement * getChildByName(StringRef tagNameToLookFor) const noexcept
void addChildElement(XmlElement *newChildElement) noexcept
double getDoubleAttribute(StringRef attributeName, double defaultReturnValue=0.0) const
XmlElement * createNewChildElement(StringRef tagName)
Iterator< GetNextElementWithTagName > getChildWithTagNameIterator(StringRef name) const
XmlElement * findParentElementOf(const XmlElement *childToSearchFor) noexcept
bool hasAttribute(StringRef attributeName) const noexcept
void deleteAllChildElements() noexcept
bool hasTagName(StringRef possibleTagName) const noexcept
Iterator< GetNextElement > getChildIterator() const
void removeAttribute(const Identifier &attributeName) noexcept
const String & getTagName() const noexcept
void removeChildElement(XmlElement *childToRemove, bool shouldDeleteTheChild) noexcept
int getIntAttribute(StringRef attributeName, int defaultReturnValue=0) const
const String & getStringAttribute(StringRef attributeName) const noexcept
void setAttribute(const Identifier &attributeName, const String &newValue)
static bool isModifier(const juce::Identifier &)
Tests whether the Identifier is of a known Modifier type.
static juce::Identifier clipTypeToXMLType(Type)
Returns an Identifier version of a TrackItem::Type.
static TrackItem::Type stringToType(const juce::String &)
Returns the TrackItem::Type of a type string.
#define TRANS(stringLiteral)
#define jassert(expression)
#define jassertfalse
constexpr Type jmax(Type a, Type b)
juce::ValueTree updateLegacyEdit(const juce::ValueTree &)
Converts old edit formats to the latest structure.
typedef uint64_t
ID for objects of type EditElement - e.g.
Logic for reading legacy tracktion edit files.
static bool isTrack(const juce::ValueTree &) noexcept
Returns true if the given ValeTree is for a known Track type.
times
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.