11#include "../../playback/audionodes/tracktion_AudioNode.h"
12#include "../../playback/audionodes/tracktion_WaveAudioNode.h"
13#include "../../playback/audionodes/tracktion_FadeInOutAudioNode.h"
14#include "../../playback/audionodes/tracktion_CombiningAudioNode.h"
17namespace tracktion {
inline namespace engine
27 void valueTreeChanged()
override { trigger(); }
29 void timerCallback()
override
31 if (juce::Component::isMouseButtonDownAnywhere())
44 : takesTree (v), clip (c)
61void CompManager::initialise()
65 refreshCachedTakeLengths();
70 lastOffset = getOffset();
74 addOrRemoveListenerIfNeeded();
91 auto numSections = activeTake.getNumChildren();
93 for (
int i = 0; i < numSections; ++i)
95 auto section = activeTake.getChild (i);
97 if (
time <
double (section.getProperty (IDs::endTime)))
107 auto numSections = activeTree.getNumChildren();
109 if (numSections == 0)
112 auto section = activeTree.getChild (0);
113 int index =
int (section.getProperty (IDs::takeIndex));
115 for (
int i = 0; i < numSections; ++i)
117 double endTime = section.getProperty (IDs::endTime);
118 auto nextSection = activeTree.getChild (i + 1);
119 int nextSectionIndex = nextSection.getProperty (IDs::takeIndex);
123 if (takeIndex == -1 || takeIndex == index || takeIndex == nextSectionIndex)
125 timeFoundAtStartOfSection = (takeIndex == nextSectionIndex);
130 section = nextSection;
131 index = nextSectionIndex;
178 auto endTime = clip.
isLooping() ? getLoopLength()
185 if (effectiveTimeMultiplier == 0.0)
188 return 1.0 / effectiveTimeMultiplier;
193 auto takeTree = takesTree.
getChild (takeIndex);
195 if (! takeTree.isValid())
198 auto hash = getBaseTakeHash (takeIndex);
199 auto lastTime = -getOffset();
201 for (
int i = takeTree.getNumChildren(); --i >= 0;)
203 auto segment = takeTree.getChild (i);
204 auto end =
static_cast<double> (segment.getProperty (IDs::endTime));
205 auto take =
static_cast<int> (segment.getProperty (IDs::takeIndex));
208 ^
static_cast<HashCode
> (take)
209 ^
static_cast<HashCode
> ((end - lastTime) * 1000.0);
222 if (! section.isValid())
225 if (section.hasType (IDs::TAKE))
227 else if (section.hasType (IDs::COMPSECTION))
228 section.setProperty (IDs::takeIndex, takeIndex, getUndoManager());
236 const int sectionIndex = section.getProperty (IDs::takeIndex);
238 if (! section.hasType (IDs::COMPSECTION)
239 || (takeIndex != -1 && sectionIndex != takeIndex))
242 auto um = getUndoManager();
243 auto previous = section.getSibling (-1);
244 auto next = section.getSibling (1);
248 if (next.isValid() && previous.isValid())
250 if (
int (previous.getProperty (IDs::takeIndex)) == sectionIndex
251 &&
int (next.getProperty (IDs::takeIndex)) == sectionIndex)
253 section.getParent().removeChild (section, um);
254 previous.getParent().removeChild (previous, um);
263 if (
int (next.getProperty (IDs::takeIndex)) != -1)
264 section.setProperty (IDs::takeIndex, -1, um);
268 else if (previous.isValid())
274 section.getParent().removeChild (section, um);
285 const double currentEndTime = section.
getProperty (IDs::endTime);
287 if (currentEndTime != newTime)
290 section.
setProperty (IDs::endTime, newTime, getUndoManager());
296 const double currentEndTime = section.
getProperty (IDs::endTime);
305 const double currentEndTime = section.
getProperty (IDs::endTime);
307 const double previousEndTime = previous.
getProperty (IDs::endTime);
308 const double timeDelta = newEndTime - currentEndTime;
310 if (newEndTime > currentEndTime)
314 if (previous.isValid())
317 else if (newEndTime < currentEndTime)
319 if (previous.isValid())
338 jassert (activeTake.isValid());
339 int insertIndex = -1;
341 for (
const auto& section : activeTake)
344 const double sectionEnd = section.getProperty (IDs::endTime);
346 if (sectionEnd > endTime)
350 auto* um = getUndoManager();
352 newSection.
setProperty (IDs::takeIndex, takeIndex, um);
353 newSection.
setProperty (IDs::endTime, endTime, um);
354 activeTake.
addChild (newSection, insertIndex, um);
370 for (
int i = takeTree.getNumChildren(); --i >= 0;)
372 auto section = takeTree.getChild (i);
373 const double endTime = section.getProperty (IDs::endTime);
375 if (timeRange.
getStart() <= endTime && endTime <= timeRange.
getEnd())
377 if (section != sectionToKeep)
380 if (sectionToKeep.
isValid() &&
double (sectionToKeep[IDs::endTime]) > endTime)
382 auto next = section.getSibling (1);
385 next.setProperty (IDs::takeIndex, section[IDs::takeIndex], getUndoManager());
401 if (! existingSection.isValid())
404 auto currentSectionIndex = existingSection.getParent().indexOf (existingSection);
406 auto* um = getUndoManager();
407 auto newSection = existingSection.createCopy();
408 newSection.setProperty (IDs::endTime,
time, um);
409 existingSection.getParent().addChild (newSection, currentSectionIndex, um);
420void CompManager::keepSectionsSortedAndInRange()
423 auto takeTree = takesTree.
getChild (activeTakeIndex);
425 if (! takeTree.isValid())
430 if (compRange.getLength() > 0.0)
433 for (
int i = takeTree.getNumChildren(); --i >= 0;)
437 if (! (sectionTimes.getStart() < compRange.getEnd() && compRange.getStart() < sectionTimes.getEnd())
438 || sectionTimes.getEnd() < compRange.getStart())
440 takeTree.removeChild (i, getUndoManager());
445 const int lastSectionIndex = takeTree.getNumChildren() - 1;
446 auto lastSection = takeTree.getChild (lastSectionIndex);
448 if (lastSection.isValid())
452 if (sectionTimes.getEnd() > compRange.getEnd())
453 lastSection.setProperty (IDs::endTime, compRange.getEnd(), getUndoManager());
461 const double firstTime = first[IDs::endTime];
462 const double secondTime = second[IDs::endTime];
464 return secondTime < firstTime ? 1 : (secondTime > firstTime ? -1 : 0);
468 EndTimeSorter sorter;
469 takeTree.
sort (sorter, getUndoManager(),
false);
477 auto newSection = createValueTree (IDs::COMPSECTION,
481 newTake.addChild (newSection, -1,
nullptr);
486void CompManager::reCacheInfo()
488 refreshCachedTakeLengths();
489 updateOffsetAndRatioFromSource();
490 addOrRemoveListenerIfNeeded();
493void CompManager::refreshCachedTakeLengths()
495 displayWarning =
false;
496 const bool autoTempo = getAutoTempo();
498 double speedRatio = 1.0;
499 double maxSourceLength = 0.0;
502 maxSourceLength =
std::max (maxSourceLength, getTakeLength (i));
507 const double sourceTempo = getSourceTempo();
509 const double takeEnd = sourceStart + maxSourceLength - getOffset();
512 auto& tempoSetting = ts.
getTempoAt (TimePosition::fromSeconds (sourceStart));
514 displayWarning = &tempoSetting != &ts.getTempoAt (TimePosition::fromSeconds (takeEnd));
515 speedRatio = tempoSetting.getBpm() / sourceTempo;
522 effectiveTimeMultiplier = 1.0 / speedRatio;
523 maxCompLength = maxSourceLength;
526void CompManager::updateOffsetAndRatioFromSource()
528 const double newOffset = getOffset();
531 if (newOffset == lastOffset && newRatio == lastTimeRatio)
534 const double ratioDiff = newRatio / lastTimeRatio;
535 const double offsetDiff = lastOffset - newOffset;
540 for (
int i = compRange.getStart(); i < compRange.getEnd(); ++i)
542 auto takeTree = takesTree.
getChild (i);
544 for (
int s = takeTree.getNumChildren(); --s >= 0;)
546 auto segment = takeTree.
getChild (s);
547 double oldEnd = segment.
getProperty (IDs::endTime);
549 double newEnd = (ratioDiff != 1.0) ? ((oldEnd - offsetDiff) * ratioDiff) + offsetDiff
550 : oldEnd + offsetDiff;
552 segment.setProperty (IDs::endTime, newEnd,
nullptr);
556 lastOffset = newOffset;
557 lastTimeRatio = newRatio;
562void CompManager::addOrRemoveListenerIfNeeded()
572 if (tree != clip.
state)
578 if (
id == IDs::start ||
id == IDs::length ||
id == IDs::offset
579 ||
id == IDs::loopStart ||
id == IDs::loopLength
580 ||
id == IDs::loopStartBeats ||
id == IDs::loopLengthBeats
581 ||
id == IDs::transpose ||
id == IDs::pitchChange
582 ||
id == IDs::elastiqueMode ||
id == IDs::elastiqueOptions
583 ||
id == IDs::autoPitch ||
id == IDs::autoTempo
584 ||
id == IDs::warpTime ||
id == IDs::speed)
587 refreshCachedTakeLengths();
588 updateOffsetAndRatioFromSource();
594CompManager::Ptr CompFactory::getCompManager (Clip& clip)
600 if (&c->getClip() == &clip && c->getTakesTree().getParent() == clip.state)
604 if (
auto wac =
dynamic_cast<WaveAudioClip*
> (&clip))
605 return new WaveCompManager (*wac);
607 if (
auto mc =
dynamic_cast<MidiClip*
> (&clip))
608 return new MidiCompManager (*mc);
614void CompFactory::addComp (CompManager& cm)
617 jassert (! comps.contains (&cm));
618 comps.addIfNotAlreadyThere (&cm);
621void CompFactory::removeComp (CompManager& cm)
624 jassert (comps.contains (&cm));
625 comps.removeAllInstancesOf (&cm);
632 double sourceTimeMultiplier_,
double offset_,
double maxLength_,
double xFade)
633 : engine (e), takesIDs (takesIDs_), takeTree (takeTree_.
createCopy()),
634 activeTakeIndex (activeTakeIndex_),
635 sourceTimeMultiplier (sourceTimeMultiplier_), offset (offset_), maxLength (maxLength_), crossfadeLength (xFade)
642 const int activeTakeIndex;
643 const double sourceTimeMultiplier, offset, maxLength, crossfadeLength;
652 : owner (o), takeIndex (t), deleteSourceFiles (delFiles)
657 void setIndex (
int newTakeIndex)
noexcept
659 takeIndex = newTakeIndex;
665 bool deleteSourceFiles;
667 void timerCallback()
override
679 : clip (c), compFile (c.edit.engine) {}
691 void setCompFile (
const AudioFile& newCompFile)
693 compFile = newCompFile;
699 enum { updateInterval = 40 };
704 bool shouldStop =
false;
706 void timerCallback()
override
708 if (strip !=
nullptr)
719 || ! clip.edit.engine.getAudioFileManager().proxyGenerator.isProxyBeingGenerated (compFile))
730 :
CompManager (owner, owner.state.getOrCreateChildWithName (IDs::TAKES, &owner.edit.getUndoManager())),
731 clip (owner), lastCompFile (clip.edit.engine), compUpdater (new
CompUpdater (clip))
733 for (
auto take : takesTree)
735 if (!
ProjectItemID::fromProperty (take, IDs::source).isValid())
736 take.setProperty (IDs::source,
ProjectItemID::createNewID (clip.edit.getProjectItemID().getProjectID()).toString(), &owner.edit.getUndoManager());
739WaveCompManager::~WaveCompManager() {}
746 for (
int i = 0; i < numTakes; ++i)
748 const AudioFile takeFile (getSourceFileForTake (i));
750 if (takeFile.isNull())
752 if (thumbnails[i] !=
nullptr)
753 thumbnails.
set (i,
nullptr);
755 thumbnails.
add (
nullptr);
759 if (
auto existing = thumbnails[i])
760 existing->setNewFile (takeFile);
762 thumbnails.
add (
new SmartThumbnail (clip.edit.engine, takeFile, comp, &clip.edit));
770 return TemporaryFileManager::getFileForCachedCompRender (clip, lastHash).getFile();
776double WaveCompManager::getTakeLength (
int takeIndex)
const
778 return getSourceFileForTake (takeIndex).getInfo().getLengthInSeconds();
781double WaveCompManager::getOffset()
const {
return clip.getPosition().getOffset().inSeconds(); }
782double WaveCompManager::getLoopLength()
const {
return clip.getLoopLength().inSeconds(); }
783bool WaveCompManager::getAutoTempo() {
return clip.getAutoTempo(); }
785double WaveCompManager::getSourceTempo()
787 const AudioFileInfo info (getSourceFileForTake (0).getInfo());
788 return clip.getLoopInfo().getBpm (info);
794 const bool warnAboutReverse = clip.getIsReversed();
799 message <<
TRANS(
"When using multiple tempo changes comp editing will not be aligned with playback.")
800 << (warnAboutReverse ?
"\n\n" :
"");
802 if (warnAboutReverse)
803 message <<
TRANS(
"Only the rendered comp will be reversed. It is best to edit your comp forwards and then reverse the clip.");
811 compUpdater->setStrip (strip);
814float WaveCompManager::getRenderProgress()
const
816 return clip.edit.engine.getAudioFileManager().proxyGenerator.getProportionComplete (lastCompFile);
823 keepSectionsSortedAndInRange();
830 if (getRenderProgress() < 1.0f || ! lastCompFile.isValid())
832 if (flattenRetrier ==
nullptr)
835 flattenRetrier->setIndex (takeIndex);
840 flattenRetrier =
nullptr;
842 auto takeTree = takesTree.
getChild (takeIndex);
844 if (
auto item = getOrCreateProjectItemForTake (takeTree))
846 auto destCompFile = getDefaultTakeFile (takeIndex);
848 if (! destCompFile.existsAsFile() || item->getSourceFile() != destCompFile)
850 jassert (destCompFile.getParentDirectory().hasWriteAccess());
851 clip.edit.engine.getAudioFileManager().releaseFile (lastCompFile);
853 if (lastCompFile.getFile().
moveFileTo (destCompFile))
855 item->setSourceFile (destCompFile);
857 else if (lastCompFile.getFile().
copyFileTo (destCompFile))
859 lastCompFile.deleteFile();
860 item->setSourceFile (destCompFile);
865 item->setSourceFile ({});
867 clip.edit.engine.getUIBehaviour().showWarningAlert (
TRANS(
"Problem flattening comp"),
868 TRANS(
"There was a problem creating the comp file at XYYX, "
869 "please ensure you have write access to this directory and try again.")
870 .replace (
"XYYX",
"\"" + destCompFile.getFullPathName() +
"\""));
877 clip.getTakes()[takeIndex] = item->getID();
878 clip.setCurrentTake (takeIndex);
879 clip.deleteAllUnusedTakes (deleteSourceFiles);
880 clip.getSourceFileReference().setToProjectFileReference (item->getID());
881 clip.setShowingTakes (
false);
900 comp.setProperty (IDs::source, dest[IDs::source],
nullptr);
901 copyValueTree (dest, comp, getUndoManager());
907void WaveCompManager::setProjectItemIDForTake (
int takeIndex,
ProjectItemID newID)
const
909 if (takeIndex < clip.getTakes().size())
910 clip.getTakes()[takeIndex] = newID;
912 auto takeTree = takesTree.
getChild (takeIndex);
915 if (takeTree.isValid())
916 takeTree.setProperty (IDs::source, newID.toString(), getUndoManager());
919ProjectItemID WaveCompManager::getProjectItemIDForTake (
int takeIndex)
const
921 return clip.getTakes()[takeIndex];
924AudioFile WaveCompManager::getSourceFileForTake (
int takeIndex)
const
926 auto& e = clip.edit.engine;
927 return AudioFile (e, e.getProjectManager().findSourceFile (getProjectItemIDForTake (takeIndex)));
930juce::File WaveCompManager::getDefaultTakeFile (
int takeIndex)
const
934 auto firstTakeItem = project->getProjectItemForID (clip.getTakes()[0]);
936 if (firstTakeItem ==
nullptr)
941 if (
auto ct = clip.getClipTrack())
942 clipNum = ct->getClips().indexOf (&clip) + 1;
945 compName <<
"_clip_" << clipNum <<
"_comp_" << (takeIndex -
getNumTakes() + 2);
947 auto firstTakeFile = firstTakeItem->getSourceFile();
948 auto baseFileName = firstTakeFile.getFileNameWithoutExtension().
upToFirstOccurrenceOf (
"_take_",
false,
false);
949 auto compFileName = baseFileName + compName;
951 return firstTakeFile.existsAsFile()
952 ? firstTakeFile.getSiblingFile (compFileName)
953 .getNonexistentSibling().withFileExtension (firstTakeFile.getFileExtension())
954 : project->getDirectoryForMedia (ProjectItem::Category::recorded)
955 .getChildFile (compFileName).withFileExtension (
"wav");
961ProjectItem::Ptr WaveCompManager::getOrCreateProjectItemForTake (
juce::ValueTree& takeTree)
967 if (
auto item = project->getProjectItemForID (getProjectItemIDForTake (takeIndex)))
970 auto destCompFile = getDefaultTakeFile (takeIndex);
972 if (
auto item = project->createNewItem (destCompFile, ProjectItem::waveItemType(),
973 destCompFile.getFileNameWithoutExtension(),
974 {}, ProjectItem::Category::recorded,
false))
977 setProjectItemIDForTake (takeIndex, item->getID());
987 auto newTake = getNewCompTree();
990 newTake.setProperty (IDs::source, newID.toString(),
nullptr);
991 newTake.setProperty (IDs::isComp,
true,
nullptr);
994 takesTree.
addChild (newTake, -1, getUndoManager());
1003 const double xFadeMs = clip.edit.engine.getPropertyStorage().getProperty (SettingID::compCrossfadeMs, 20.0);
1016 const int blockSize = 32768;
1018 auto crossfadeLength = context.crossfadeLength;
1019 auto halfCrossfade = crossfadeLength / 2.0;
1022 auto timeRatio = context.sourceTimeMultiplier;
1023 auto offset = context.offset / timeRatio;
1024 double startTime = 0.0;
1026 for (
int i = 0; i < numSegments; ++i)
1031 auto compSegment = context.takeTree.
getChild (i);
1032 auto takeIndex = (
int) compSegment.getProperty (IDs::takeIndex);
1033 auto endTime =
double (compSegment.getProperty (IDs::endTime)) / timeRatio;
1048 fadeIn = { segmentTimes.getStart(), segmentTimes.getStart() + crossfadeLength };
1050 if (i != (numSegments - 1))
1051 fadeOut = { segmentTimes.getEnd() - crossfadeLength, segmentTimes.getEnd() };
1053 if (! (fadeIn.isEmpty() && fadeOut.isEmpty()))
1054 node =
new FadeInOutAudioNode (node, fadeIn, fadeOut, AudioFadeCurve::convex, AudioFadeCurve::convex);
1056 compNode.addInput ({
std::max (0.0, segmentTimes.getStart()),
1057 std::min (segmentTimes.getEnd(), context.maxLength) },
1061 startTime = endTime;
1069 compNode.getAudioNodeProperties (props);
1076 allNodes.
add (&compNode);
1087 compNode.prepareAudioNodeToPlay (info);
1095 &renderingBuffer, renderingBufferChannels, 0, blockSize,
1097 AudioRenderContext::playheadJumped,
true);
1099 localPlayhead.setPosition (takeRange.
getStart());
1106 auto streamTime = takeRange.
getStart();
1112 auto blockEnd =
std::min (streamTime + blockLength, takeRange.
getEnd());
1115 auto numSamplesDone = (
int)
std::min (samplesToWrite, (SampleCount) blockSize);
1116 samplesToWrite -= numSamplesDone;
1120 if (numSamplesDone > 0)
1122 compNode.prepareForNextBlock (rc);
1123 compNode.renderOver (rc);
1126 rc.
continuity = AudioRenderContext::contiguous;
1127 streamTime = blockEnd;
1136 if (numSamplesDone <= 0 || ! writer.
isOpen()
1137 || ! writer.
appendBuffer (renderingBuffer, numSamplesDone))
1142 localPlayhead.stop();
1156 : GeneratorJob (comp), engine (wc.edit.
engine), clipID (wc.
itemID),
1159 setName (
TRANS(
"Creating Comp") +
": " + wc.
getName());
1167 bool render()
override
1172 .withFileExtension (proxy.getFile().getFileExtension()));
1174 bool ok = render (tempFile);
1178 ok = proxy.deleteFile();
1181 ok = tempFile.getFile().moveFileTo (proxy.getFile());
1185 tempFile.deleteFile();
1198 for (
auto& takeID : context->takesIDs)
1202 if (takeFile.existsAsFile())
1206 if (! takeFile.existsAsFile())
1209 const AudioFile firstTakeAudioFile (engine, takeFile);
1213 if (sourceInfo.metadata.
getValue (
"MetaDataSource",
"None") ==
"AIFF")
1214 sourceInfo.metadata.
clear();
1217 sourceInfo.numChannels, sourceInfo.sampleRate,
1218 std::max (16, sourceInfo.bitsPerSample),
1219 sourceInfo.metadata, 0);
1222 && context !=
nullptr
1223 && WaveCompManager::renderTake (*context, writer, *
this, progress);
1229static void beginCompGeneration (
WaveAudioClip& clip,
int takeIndex)
1232 auto& cm = clip.getCompManager();
1234 clip.edit.engine.getAudioFileManager()
1235 .proxyGenerator.beginJob (
new CompGeneratorJob (clip, TemporaryFileManager::getFileForCachedCompRender (clip, cm.getTakeHash (takeIndex))));
1238void WaveCompManager::timerCallback()
1242 if (! clip.hasAnyTakes())
1251 if (
isTakeComp (lastRenderedTake) && hash != lastHash)
1254 clip.edit.engine.getAudioFileManager().proxyGenerator
1255 .deleteProxy (TemporaryFileManager::getFileForCachedCompRender (clip, lastHash));
1258 lastRenderedTake = takeIndex;
1261 lastCompFile = TemporaryFileManager::getFileForCachedCompRender (clip, lastHash);
1262 const bool isComp =
isTakeComp (lastRenderedTake);
1264 if (isComp && (! lastCompFile.isValid()))
1266 auto takeTree = takesTree.
getChild (lastRenderedTake);
1267 beginCompGeneration (clip, lastRenderedTake);
1268 compUpdater->setCompFile (lastCompFile);
1275 if (clip.getCurrentSourceFile() != lastCompFile.getFile())
1277 clip.setCurrentSourceFile (lastCompFile.getFile());
1278 SelectionManager::refreshAllPropertyPanels();
1284MidiCompManager::MidiCompManager (MidiClip& owner)
1285 :
CompManager (owner, owner.state.getOrCreateChildWithName (IDs::COMPS, &owner.edit.getUndoManager())),
1286 clip (owner), midiTakes (owner.state.getChildWithName (IDs::TAKES))
1295MidiCompManager::~MidiCompManager() {}
1297void MidiCompManager::initialise()
1301 const int numTakes (clip.getNumTakes (
true));
1302 auto um = getUndoManager();
1305 for (
int i = 0; i < numTakes; ++i)
1307 auto takeTree = takesTree.
getChild (takeIndex);
1311 if (
auto ml = clip.getTakeSequence (i))
1314 newTake.setProperty (IDs::isComp, ml->isCompList(), um);
1315 takesTree.
addChild (newTake, takeIndex, um);
1326 lastHash = ! lastHash;
1327 CompManager::initialise();
1330MidiList* MidiCompManager::getSequenceLooped (
int index)
1332 auto sourceSequence = clip.getTakeSequence (index);
1334 if (! clip.isLooping())
1335 return sourceSequence;
1337 if (
auto ml = cachedLoopSequences[index])
1340 if (sourceSequence ==
nullptr)
1343 return cachedLoopSequences.set (index, clip.createSequenceLooped (*sourceSequence).release());
1347HashCode MidiCompManager::getBaseTakeHash (
int takeIndex)
const
1350 ^
static_cast<HashCode
> (clip.getLoopLengthBeats().inBeats() * 153.0)
1351 ^
static_cast<HashCode
> (clip.getLoopStartBeats().inBeats() * 264.0);
1354double MidiCompManager::getTakeLength (
int takeIndex)
const
1356 if (clip.isLooping())
1357 return clip.getLoopLengthBeats().inBeats();
1359 if (
auto ml = clip.getTakeSequence (takeIndex))
1360 return ml->getLastBeatNumber().inBeats();
1365void MidiCompManager::discardCachedData()
1367 cachedLoopSequences.clear();
1377 keepSectionsSortedAndInRange();
1379 if (! clip.hasAnyTakes())
1388 if (hash == lastHash)
1391 lastRenderedTake = takeIndex;
1399 if (clip.getCurrentTake() != takeIndex)
1400 clip.setCurrentTake (takeIndex);
1403 clip.edit.restartPlayback();
1408 if (clip.getCurrentTake() != takeIndex)
1409 clip.setCurrentTake (takeIndex);
1411 clip.deleteAllUnusedTakesConfirmingWithUser();
1417 auto newTake = getNewCompTree();
1418 newTake.setProperty (IDs::isComp,
true,
nullptr);
1424 if (
auto ml = clip.getTakeSequence (clip.getNumTakes (
true) - 1))
1425 ml->setCompList (
true);
1429 takesTree.
addChild (newTake, -1, getUndoManager());
1430 clip.setCurrentTake (clip.getNumTakes (
true) - 1);
1441 clip.edit.engine.getUIBehaviour().showWarningMessage (
TRANS(
"There was a problem creating the MIDI comp"));
1445 if (
auto dest = clip.getTakeSequence (lastRenderedTake))
1448 auto um = getUndoManager();
1453 const auto loopStart = toDuration (clip.getLoopStartBeats());
1456 for (
int i = 0; i < numSegments; ++i)
1458 auto compSegment = takeTree.
getChild (i);
1459 const auto takeIndex =
static_cast<int> (compSegment.getProperty (IDs::takeIndex));
1460 const auto endBeat = BeatPosition::fromBeats (
static_cast<double> (compSegment.getProperty (IDs::endTime)));
1464 if (
auto src = getSequenceLooped (takeIndex))
1466 const BeatRange beats (startBeat, endBeat);
1468 for (
auto n : src->getNotes())
1470 auto nBeats = n->getRangeBeats();
1472 if (nBeats.getStart() > endBeat)
1475 auto newRange = beats.getIntersectionWith (nBeats);
1477 if (! newRange.isEmpty())
1479 MidiNote newNote (n->state.createCopy());
1480 newRange = newRange + loopStart;
1481 newNote.setStartAndLength (newRange.getStart(), newRange.getLength(),
nullptr);
1482 dest->addNote (newNote, um);
1486 for (
auto e : src->getControllerEvents())
1488 auto b = e->getBeatPosition();
1494 dest->addControllerEvent (b + loopStart, e->getType(), e->getControllerValue(), e->getMetadata(), um);
1497 for (
auto e : src->getSysexEvents())
1499 auto b = e->getBeatPosition();
1505 dest->addSysExEvent (e->getMessage(), b + loopStart, um);
1514 startBeat = endBeat;
1517 dest->setCompList (
true);
void add(const ElementType &newElement)
int getNumChannels() const noexcept
static AudioChannelSet JUCE_CALLTYPE stereo()
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
bool moveFileTo(const File &targetLocation) const
bool copyFileTo(const File &targetLocation) const
ObjectClass * set(int indexToChange, ObjectClass *newObject, bool deleteOldElement=true)
ObjectClass * add(ObjectClass *newObject)
int64 nextInt64() noexcept
static Random & getSystemRandom() noexcept
constexpr Range expanded(ValueType amount) const noexcept
constexpr ValueType getStart() const noexcept
constexpr ValueType getEnd() const noexcept
ValueType clipValue(const ValueType value) const noexcept
constexpr ValueType getLength() const noexcept
constexpr bool contains(const ValueType position) const noexcept
String getValue(StringRef, const String &defaultReturnValue) const
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
static String toHexString(IntegerType number)
bool shouldExit() const noexcept
void stopTimer() noexcept
void startTimer(int intervalInMilliseconds) noexcept
bool hasType(const Identifier &typeName) const noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
ValueTree getChild(int index) const
int getNumChildren() const noexcept
bool isValid() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, 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
const var & getProperty(const Identifier &name) const noexcept
ValueTree createCopy() const
void removeListener(Listener *listener)
ValueTree getSibling(int delta) const noexcept
void sort(ElementComparator &comparator, UndoManager *undoManager, bool retainOrderOfEquivalentItems)
bool hasProperty(const Identifier &name) const noexcept
Smart wrapper for writing to an audio file.
double getSampleRate() const noexcept
Returns the sample rate of the writer, should only be called on an open writer.
bool appendBuffer(juce::AudioBuffer< float > &buffer, int numSamples)
Appends an AudioBuffer to the file.
int getNumChannels() const noexcept
Returns the num channels of the writer, should only be called on an open writer.
bool isOpen() const noexcept
Returns true if the file is open and ready to write to.
void closeForWriting()
Deletes the writer and releases the file handle.
Base class for nodes in an audio playback graph.
virtual juce::String getName() const override
Returns the name of the clip.
void changed() override
This should be called to send a change notification to any SelectableListeners that are registered wi...
virtual bool hasAnyTakes() const
Returns true if this clip has any takes.
virtual bool isLooping() const
Returns true if this clip is currently looping.
juce::ValueTree state
The ValueTree of the Clip state.
double getSpeedRatio() const noexcept
Returns the speed ratio i.e.
virtual void setCurrentTake(int)
Sets a given take index to be the current take.
virtual juce::StringArray getTakeDescriptions() const
Returns the descriptions of any takes.
ClipPosition getPosition() const override
Returns the ClipPosition on the parent Track.
virtual int getCurrentTake() const
Returns the current take index.
An AudioNode that mixes a sequence of clips of other nodes.
~CompManager() override
Destructor.
int findSectionWithEndTime(juce::Range< double > range, int takeIndex, bool &timeFoundAtStartOfSection) const
Returns the index of the section whose end lies within the given time range.
void moveSectionToEndAt(juce::ValueTree §ion, double newEndTime)
Moves a section to an absolute end time also moving the previous section's end time by the same ammou...
virtual void triggerCompRender()=0
Triggers the render of the comp.
bool shouldDisplayWarning() const noexcept
Returns true if the source should display a warning about using multi-tempo changes.
juce::ValueTree getActiveTakeTree() const
Returns the active take tree.
void setActiveTakeIndex(int index)
Sets the active take index.
juce::String getTakeName(int index) const
Returns the name of a take.
double getSpeedRatio() const
Returns the effective speed ratio used for displaying waveforms.
HashCode getTakeHash(int takeIndex) const
Returns a hash code representing a take.
int getActiveTakeIndex() const
Returns the active take index.
juce::ValueTree splitSectionAtTime(double time)
Find the current section at the given time and splits it in two ready for a new comp section.
void changeSectionIndexAtTime(double time, int takeIndex)
Changes the index of the active comp's section at a given time.
void removeSectionsWithinRange(juce::Range< double > timeRange, const juce::ValueTree §ionToKeep)
Removes all sections which lie within the given time range.
virtual juce::ValueTree addNewComp()=0
Adds a new comp to the end of the takes list optionally making it active.
double getSourceTimeMultiplier() const
Returns the current time multiplier in use by the source, either the speed ratio or auto tempo ratio.
juce::Range< double > getCompRange() const
Returns the time range available for comping i.e.
double getMaxCompLength() const
Returns the maximum length that a comp can be.
int getNumComps() const
Returns the number of comps that are comps.
virtual void flattenTake(int takeIndex, bool deleteSourceFiles)=0
Should flatten the comp and remove all other takes.
void removeSectionIndexAtTime(double time, int takeIndex)
Removes a section from the comp at the given time if the section is at the given take index.
bool isCurrentTakeComp() const
Returns true if the current take is a comp.
juce::ValueTree addSection(int takeIndex, double endTime)
Adds a new section at a given time and returns the index of it.
void moveSection(juce::ValueTree §ion, double timeDelta)
Moves a section by the specified time delta also moving the previous section's end time by the same a...
juce::Range< double > getSectionTimes(const juce::ValueTree &) const
Returns the time range a given section occupies for a given take.
CompManager(Clip &, const juce::ValueTree &)
Creates a CompManager for a given clip.
juce::ValueTree getSection(int takeIndex, int sectionIndex) const
Returns the section at the given index of a given take.
void moveSectionEndTime(juce::ValueTree §ion, double newTime)
Moves a section's end time to the new time specified.
int getNumTakes() const
Returns the number of takes that are not comps.
juce::ValueTree findSectionAtTime(double time)
Returns either the section for the current comp at a given time or if a whole take is being used the ...
void removeSection(const juce::ValueTree §ionToRemove)
Removes a section from the active comp if it is within range.
bool isTakeComp(int takeIndex) const
Returns true if the given take at an index is a comp.
const EditItemID itemID
Every EditItem has an ID which is unique within the edit.
static constexpr double maximumLength
The maximum length an Edit can be.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
juce::UndoManager & getUndoManager() noexcept
Returns the juce::UndoManager used for this Edit.
Engine & engine
A reference to the Engine.
The Engine is the central class for all tracktion sessions.
CompFactory & getCompFactory() const
Returns the CompFactory instance.
AudioFileFormatManager & getAudioFileFormatManager() const
Returns the AudioFileFormatManager that maintains a list of available audio file formats.
AudioFileManager & getAudioFileManager() const
Returns the AudioFileManager instance.
ProjectManager & getProjectManager() const
Returns the ProjectManager instance.
An AudioNode that fades its input node in/out at given times.
@ none
No automation, add the sequence as plain MIDI with the channel of the clip.
An ID representing one of the items in a Project.
static ProjectItemID createNewID(int projectID) noexcept
Generates a new ID for a given project.
juce::File findSourceFile(ProjectItemID)
tries to find the media file used by a particular object.
SmartThumnail automatically tracks changes to an AudioFile and will update its cache if the file chan...
TempoSetting & getTempoAt(TimePosition) const
Returns the TempoSetting at the given position.
An audio clip that uses an audio file as its source.
WaveCompManager & getCompManager()
Returns the WaveCompManager for this clip.
An AudioNode that plays back a wave file.
juce::ValueTree pasteComp(const juce::ValueTree &compTree) override
Pastes an existing comp to this manager and returns the newly added tree.
CompRenderContext * createRenderContext() const
Returns a context to render the current taste of this comp.
void flattenTake(int takeIndex, bool deleteSourceFiles) override
Should flatten the comp and remove all other takes.
void triggerCompRender() override
Triggers the render of the comp.
void setStripToUpdate(juce::Component *strip) override
Sets a component to be updated during render processes.
juce::ValueTree addNewComp() override
Adds a new comp to the end of the takes list optionally making it active.
static bool renderTake(CompRenderContext &, AudioFileWriter &, juce::ThreadPoolJob &, std::atomic< float > &progress)
Renders the comp using the given writer and ThreadPoolJob.
void updateThumbnails(juce::Component &, juce::OwnedArray< SmartThumbnail > &thumbnails) const
Updates an array of thumbnails so they represent the takes and are in the correct order etc.
juce::File getCurrentCompFile() const
Returns the current comp file.
#define TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
int roundToInt(const FloatType value) noexcept
Project::Ptr getProjectForEdit(const Edit &e)
Tries to find the project that contains this edit (but may return nullptr!)
Holds some really basic properties of a node.
Passed into AudioNodes when they are being initialised, to give them useful contextual information th...
size_t hash(size_t seed, const T &v)
Hashes a type with a given seed and returns the new hash value.
Represents a position in beats.
constexpr double inSeconds() const
Returns the TimeDuration as a number of seconds.
constexpr double inSeconds() const
Returns the TimePosition as a number of seconds.
int bufferNumSamples
The number of samples to write into the audio buffer.
int continuity
A set of flags to indicate what the relationship is between this block and the previous one.
legacy::EditTimeRange streamTime
The time window which needs to be rendered into the current block.
TimePosition getStart() const
Returns the start time.
TimeDuration getOffset() const
Returns the offset.
ID for objects of type EditElement - e.g.
Updates a strip during a comp render and notifies the Clip when it finishes.
Re-calls flatten take to allow the comp time to finish rendering if needed.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.