11namespace tracktion {
inline namespace engine
29 for (
auto c : objects)
31 c->flushStateToValueTree();
32 c->setParent (
nullptr);
48 newClip->incReferenceCount();
57 void deleteObject (
Clip* c)
override
63 c->decReferenceCount();
66 void newObjectAdded (
Clip* c)
override
68 objectAddedOrRemoved (c);
74 void objectRemoved (
Clip* c)
override { objectAddedOrRemoved (c); }
75 void objectOrderChanged()
override { objectAddedOrRemoved (
nullptr); }
77 void objectAddedOrRemoved (
Clip* c)
99 if (
id == IDs::start ||
id == IDs::length)
109 void handleAsyncUpdate()
override
120 if (i == IDs::AUTOMATIONTRACK)
return 0;
121 if (Clip::isClipState (i))
return 1;
122 if (i == IDs::PLUGIN)
return 2;
123 if (i == IDs::OUTPUTDEVICES)
return 3;
124 if (i == IDs::LFOS)
return 4;
131 auto priority = getPriority (first.
getType()) - getPriority (second.
getType());
136 if (Clip::isClipState (first) && Clip::isClipState (second))
138 double t1 = first[IDs::start];
139 double t2 = second[IDs::start];
140 return t1 < t2 ? -1 : (t2 < t1 ? 1 : 0);
148 state.sort (clipSorter, um,
true);
151 void editFinishedLoading()
153 editLoadedCallback =
nullptr;
155 for (
auto c : objects)
157 acb->updateAutoCrossfadesAsync (
false);
184 assert (clipList &&
"You must call initialiseClipOwner before any other methods");
185 return clipList->objects;
212 EditItemID itemID, ClipPosition position)
214 addValueTreeProperties (state,
216 IDs::start, position.getStart().inSeconds(),
217 IDs::length, position.getLength().inSeconds(),
218 IDs::offset, position.getOffset().inSeconds());
220 itemID.writeID (state,
nullptr);
224 EditItemID itemID, ClipPosition position)
227 updateClipState (state, name, itemID, position);
235 for (
int i = in.
length(); --i >= 0;)
260 if (clipState.
hasType (IDs::MIDICLIP))
262 setPropertyIfMissing (clipState, IDs::sync, engineBehaviour.areMidiClipsRemappedWhenTempoChanges()
265 else if (clipState.
hasType (IDs::AUDIOCLIP) || clipState.
hasType (IDs::EDITCLIP))
269 auto sourceFile = SourceFileReference::findFileFromString (edit, clipState[IDs::source]);
271 if (sourceFile.exists())
273 auto loopInfo =
AudioFile (edit.engine, sourceFile).getInfo().loopInfo;
275 if (loopInfo.getRootNote() != -1)
276 clipState.
setProperty (IDs::autoPitch,
true,
nullptr);
278 if (loopInfo.isLoopable())
280 clipState.
setProperty (IDs::autoTempo,
true,
nullptr);
281 clipState.
setProperty (IDs::stretchMode,
true,
nullptr);
284 auto& ts = edit.tempoSequence;
286 auto startBeat = ts.toBeats (TimePosition::fromSeconds (
static_cast<double> (clipState[IDs::start])));
287 auto endBeat = startBeat + BeatDuration::fromBeats (loopInfo.getNumBeats());
288 auto newLength = ts.toTime (endBeat) - ts.toTime (startBeat);
290 clipState.
setProperty (IDs::length, newLength.inSeconds(),
nullptr);
293 auto loopSate = loopInfo.state;
295 if (loopSate.getNumProperties() > 0 || loopSate.getNumChildren() > 0)
296 clipState.
addChild (loopSate.createCopy(), -1,
nullptr);
303 clipState.
setProperty (IDs::sync, (
int) engineBehaviour.areAutoTempoClipsRemappedWhenTempoChanges()
306 clipState.
setProperty (IDs::sync, (
int) engineBehaviour.areAudioClipsRemappedWhenTempoChanges()
311 if (edit.engine.getPropertyStorage().getProperty (SettingID::xFade, 0))
312 clipState.
setProperty (IDs::autoCrossfade,
true,
nullptr);
315 if (clipOwner.
getClips().
size() < engineBehaviour.getEditLimits().maxClipsInTrack)
318 if (
auto existingClip = clipSlot->getClip())
319 existingClip->removeFromParent();
327 if (newClip->getColour() == newClip->getDefaultColour())
329 auto col = at->getColour();
331 float hue = col.isTransparent() ? ((at->getIndexInEditTrackList() % 18) * 1.0f / 18.0f) : col.getHue();
332 newClip->setColour (newClip->getDefaultColour().withHue (hue));
337 if (engineBehaviour.autoAddClipEdgeFades())
339 acb->applyEdgeFades();
341 const auto defaults = engineBehaviour.getClipDefaults();
344 acb->setUsesProxy (defaults.useProxyFile);
346 if (! clipState.
hasProperty (IDs::resamplingQuality))
347 acb->setResamplingQuality (defaults.resamplingQuality);
352 if (newClip->getColour() == newClip->getDefaultColour())
354 auto col = cs->track.getColour();
355 float hue = col.isTransparent() ? ((cs->track.getIndexInEditTrackList() % 18) * 1.0f / 18.0f) : col.getHue();
356 newClip->setColour (newClip->getDefaultColour().withHue (hue));
361 if (acb->effectsEnabled())
362 acb->enableEffects (
false,
false);
364 acb->setUsesProxy (
false);
365 acb->setAutoTempo (
true);
366 acb->setStart (0_tp,
false,
true);
368 if (! acb->isLooping())
369 acb->setLoopRangeBeats ({ 0_bp, acb->getLengthInBeats() });
371 else if (
auto mc =
dynamic_cast<MidiClip*
> (newClip))
373 mc->setUsesProxy (
false);
374 mc->setStart (0_tp,
false,
true);
376 if (! mc->isLooping ())
377 mc->setLoopRangeBeats (mc->getEditBeatRange());
379 else if (
auto sc =
dynamic_cast<StepClip*
> (newClip))
381 sc->setStart (0_tp,
false,
true);
383 if (! sc->isLooping())
384 sc->setLoopRangeBeats ({ 0_bp, sc->getLengthInBeats() });
393 edit.engine.getUIBehaviour().showWarningMessage (
TRANS(
"Can't add any more clips to this track!"));
407 if (position.getStart() >= Edit::getMaximumEditEnd())
410 if (position.time.getEnd() > Edit::getMaximumEditEnd())
411 position.time.getEnd() = Edit::getMaximumEditEnd();
413 if (
auto track =
dynamic_cast<Track*
> (&parent))
414 track->setFrozen (
false, Track::groupFreeze);
416 if (deleteExistingClips == DeleteExistingClips::yes)
419 auto newClipID = edit.createNewItemID();
425 jassert (stateToUse.
hasType (TrackItem::clipTypeToXMLType (type)));
426 newState = stateToUse;
427 clip_owner::updateClipState (newState, name, newClipID, position);
431 newState = clip_owner::createNewClipState (name, type, newClipID, position);
436 if (allowSpottingAdjustment)
437 newClip->setStart (
std::max (0_tp, newClip->getPosition().getStart() - toDuration (newClip->getSpottingPoint())),
false,
false);
452 return insertNewClip (parent, type, TrackItem::getSuggestedNameForNewItem (type), pos);
459 if (
auto newClip =
insertClipWithState (parent, {}, name, type, position, DeleteExistingClips::no,
false))
472 if (
auto source = proj->createNewItem (sourceFile, ProjectItem::waveItemType(),
473 name, {}, ProjectItem::Category::imported,
true))
474 return insertWaveClip (parent, name, source->getID(), position, deleteExistingClips);
482 auto newState = clip_owner::createNewClipState (name, TrackItem::Type::wave, edit.createNewItemID(), position);
483 const bool useRelativePath = edit.filePathResolver && edit.editFileRetriever && edit.editFileRetriever().existsAsFile();
484 newState.setProperty (IDs::source, SourceFileReference::findPathFromFile (edit, sourceFile, useRelativePath),
nullptr);
486 if (
auto c =
insertClipWithState (parent, newState, name, TrackItem::Type::wave, position, deleteExistingClips,
false))
505 auto newState = clip_owner::createNewClipState (name, TrackItem::Type::wave, edit.createNewItemID(), position);
506 newState.setProperty (IDs::source, sourceID.toString(),
nullptr);
508 if (
auto c =
insertClipWithState (parent, newState, name, TrackItem::Type::wave, position, deleteExistingClips,
false))
521 if (
auto t =
dynamic_cast<AudioTrack*
> (&parent))
522 if (! t->containsAnyMIDIClips())
523 t->setVerticalScaleToDefault();
525 if (
auto c =
insertNewClip (parent, TrackItem::Type::midi, name, position))
527 if (
auto mc =
dynamic_cast<MidiClip*
> (c))
538 return insertMIDIClip (parent, TrackItem::getSuggestedNameForNewItem (TrackItem::Type::midi), position);
545 auto name = TrackItem::getSuggestedNameForNewItem (TrackItem::Type::edit);
546 auto newState = clip_owner::createNewClipState (name, TrackItem::Type::edit, parent.
getClipOwnerEdit().
createNewItemID(), { position, TimeDuration() });
547 newState.setProperty (IDs::source, sourceID.toString(),
nullptr);
549 if (
auto c =
insertClipWithState (parent, newState, name, TrackItem::Type::edit, { position, 0_td }, DeleteExistingClips::no,
false))
551 if (
auto ec =
dynamic_cast<EditClip*
> (c))
566 if (
auto track =
dynamic_cast<Track*
> (&parent))
568 track->setFrozen (
false, Track::groupFreeze);
569 track->setFrozen (
false, Track::individualFreeze);
573 Clip::Array clipsToDo;
576 if (c->getPosition().time.overlaps (range))
579 for (
int i = clipsToDo.size(); --i >= 0;)
590 if (
auto track =
dynamic_cast<Track*
> (c.getParent()))
592 track->setFrozen (
false, Track::groupFreeze);
593 track->setFrozen (
false, Track::individualFreeze);
596 auto pos = c.getPosition();
598 if (range.contains (pos.time))
600 c.removeFromParent();
602 else if (pos.getStart() < range.getStart() && pos.getEnd() > range.getEnd())
604 if (
auto newClip =
split (c, range.getStart()))
606 newClip->setStart (range.getEnd(),
true,
false);
607 c.setEnd (range.getStart(),
true);
610 newClips.
add (newClip);
615 c.trimAwayOverlap (range);
627 Clip::Array clipsToDo;
630 if (c->getPosition().time.contains (
time))
633 for (
auto c : clipsToDo)
642 auto parent = clip.getParent();
644 if (parent ==
nullptr)
647 auto& edit = clip.edit;
649 if (
auto track =
dynamic_cast<Track*
> (parent))
650 track->setFrozen (
false, Track::groupFreeze);
652 if (clip.getPosition().time.reduced (0.001s).contains (
time)
653 && ! clip.isGrouped())
655 auto newClipState = clip.state.createCopy();
656 edit.createNewItemID().writeID (newClipState,
nullptr);
665 acb->setFadeOut ({});
669 const bool isChord =
dynamic_cast<ChordClip*
> (&clip) !=
nullptr;
671 clip.setEnd (
time,
true);
674 if (
auto mc1 =
dynamic_cast<MarkerClip*
> (&clip))
676 if (
auto mc2 =
dynamic_cast<MarkerClip*
> (newClip))
678 auto id = edit.getMarkerManager().getNextUniqueID (mc1->getMarkerID());
679 mc2->setMarkerID (
id);
681 if (mc1->getName() == (
TRANS(
"Marker") +
" " +
juce::String (mc1->getMarkerID())))
684 mc2->setName (clip_owner::incrementLastDigit (mc1->getName()));
692 / newClip->getLoopLengthBeats()) + 0.00001));
696 auto newOffsetBeats = newClip->getOffsetInBeats() - (newClip->getLoopLengthBeats() * extra);
697 auto offset = TimeDuration::fromSeconds (newOffsetBeats.inBeats() / edit.tempoSequence.getBeatsPerSecondAt (newClip->getPosition().getStart()));
699 newClip->setOffset (offset);
740 if (
auto track =
dynamic_cast<const Track*
> (&co))
751 if (
auto track =
dynamic_cast<const Track*
> (&co))
760 || (
isAutomationTrack (track) && track.getParentTrack() !=
nullptr && track.getParentTrack()->isMasterTrack());
void addArray(const Type *elementsToAdd, int numElementsToAdd)
int size() const noexcept
void add(const ElementType &newElement)
void triggerAsyncUpdate()
static bool isDigit(char character) noexcept
int length() const noexcept
String dropLastCharacters(int numberToDrop) const
int getTrailingIntValue() const noexcept
bool isPerformingUndoRedo() const
bool hasType(const Identifier &typeName) const noexcept
bool isValid() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
ValueTree getParent() const noexcept
const var & getProperty(const Identifier &name) const noexcept
Identifier getType() const noexcept
ValueTree getChildWithName(const Identifier &type) const
bool hasProperty(const Identifier &name) const noexcept
A track similar to the MarkerTrack but can be used to move sections of an Edit around.
Base class for Clips that produce some kind of audio e.g.
Base class for items that can contain clips.
ClipOwner()
Constructs an empty ClipOwner.
const juce::Array< Clip * > & getClips() const
Returns the clips this owner contains.
virtual void clipOrderChanged()=0
Called when clips have moved times so that their order has changed.
void initialiseClipOwner(Edit &, juce::ValueTree clipParentState)
Must be called once from the subclass constructor to init the clip owner.
virtual Selectable * getClipOwnerSelectable()=0
Must return the selectable if this ClipOwner is one.
virtual Edit & getClipOwnerEdit()=0
Must return the Edit this ClipOwner belongs to.
virtual ~ClipOwner()
Destructor.
virtual void clipCreated(Clip &)=0
Called when a clip is created which could be during Edit load.
virtual void clipAddedOrRemoved()=0
Called when a clip is added or removed.
virtual void clipPositionChanged()=0
Called when a clip start or end position has changed.
virtual juce::ValueTree & getClipOwnerState()=0
Must return the state of this ClipOwner.
Represents a slot on a track that a Clip can live in to be played as a launched clip.
static bool isClipState(const juce::ValueTree &)
Checks whether a ValueTree is some kind of clip state.
void setStart(TimePosition newStart, bool preserveSync, bool keepLength)
Sets the start time of the clip.
static Ptr createClipForState(const juce::ValueTree &, ClipOwner &targetParent)
Creates a clip for a given ValueTree representation.
@ syncBarsBeats
Sync to beats.
@ syncAbsolute
Sync to abslute time.
A clip that can contain multiple other clips and mix their output together.
This is the main source of an Edit clip and is responsible for managing its properties.
The Tracktion Edit class!
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
bool isLoading() const
Returns true if the Edit's not yet fully loaded.
juce::UndoManager & getUndoManager() noexcept
Returns the juce::UndoManager used for this Edit.
EditItemID createNewItemID() const
Returns a new EditItemID to use for a new EditItem.
Engine & engine
A reference to the Engine.
virtual void newClipAdded(Clip &, bool fromRecording)
Returns the defaults to be applied to new clips.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
A track to represent the master plugins.
An ID representing one of the items in a Project.
A track to represent the "global" items such as tempo, key changes etc.
@ elastiquePro
Elastique Pro good all round (.
Type
Defines the types of item that can live on Track[s].
@ unknown
A placeholder for unknown items.
static juce::Identifier clipTypeToXMLType(Type)
Returns an Identifier version of a TrackItem::Type.
Base class for tracks which contain clips and plugins and can be added to Edit[s].
bool isRecordingStopping() const
Returns true if a recording is currently being stopped.
An audio clip that uses an audio file as its source.
#define TRANS(stringLiteral)
int roundToInt(const FloatType value) noexcept
bool containsAnyMIDIClips(const ClipOwner &co)
Returns true if the clip owner contains any MIDI clips.
bool canContainMIDI(const ClipOwner &co)
Returns true if this Track can contain MidiClip[s].
Clip * findClipForState(ClipOwner &co, const juce::ValueTree &v)
Returns a clip with the given state if the ClipOwner contains it.
bool canContainAudio(const ClipOwner &co)
Returns true if this Track can contain WaveAudioClip[s].
Clip * findClipForID(ClipOwner &co, EditItemID id)
Returns a clip with the given ID if the ClipOwner contains it.
bool isOnTop(const Track &track)
Returns true if this a global Track and should be on top of others.
bool isAutomationTrack(const Track &t)
Returns true if this is an AutomationTrack.
bool isFolderTrack(const Track &t)
Returns true if this is a FolderTrack.
juce::Array< Clip * > deleteRegion(ClipOwner &parent, TimeRange range)
Removes a region of a ClipOwner and returns any newly created clips.
bool isAudioTrack(const Track &t)
Returns true if this is an AudioTrack.
bool isArrangerTrack(const Track &t)
Returns true if this is an ArrangerTrack.
MidiClip::Ptr insertMIDIClip(ClipOwner &parent, const juce::String &name, TimeRange position)
Inserts a new MidiClip into the ClipOwner's clip list.
juce::ReferenceCountedObjectPtr< WaveAudioClip > insertWaveClip(ClipOwner &parent, const juce::String &name, const juce::File &sourceFile, ClipPosition position, DeleteExistingClips deleteExistingClips)
Inserts a new WaveAudioClip into the ClipOwner's clip list.
Project::Ptr getProjectForEdit(const Edit &e)
Tries to find the project that contains this edit (but may return nullptr!)
bool isMarkerTrack(const Track &t)
Returns true if this is a MarkerTrack.
EditClip::Ptr insertEditClip(ClipOwner &parent, TimeRange position, ProjectItemID sourceID)
Inserts a new EditClip into the ClipOwner's clip list.
bool isChordTrack(const Track &t)
Returns true if this is a ChordTrack.
Clip * insertNewClip(ClipOwner &parent, TrackItem::Type type, const juce::String &name, EditTimeRange pos)
Inserts a new clip with the given type and name.
DeleteExistingClips
Determines behaviour for overwriting clips.
Clip * insertClipWithState(ClipOwner &clipOwner, juce::ValueTree clipState)
Inserts a clip with the given state in to the ClipOwner's clip list.
bool isMasterTrack(const Track &t)
Returns true if this is a MasterTrack.
juce::Array< Clip * > split(ClipOwner &parent, TimePosition time)
Splits the given clp owner at the time and returns any newly created clips.
bool isTempoTrack(const Track &t)
Returns true if this is a TempoTrack.
Represents a duration in beats.
Represents a position in real-life time.
Represents the position of a clip on a timeline.
ID for objects of type EditElement - e.g.
Represents a time range in an Edit stored as either time or beats.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.