11namespace tracktion {
inline namespace engine
14template<
typename ObjectType>
23 void newObjectAdded (ObjectType*)
override { sendChange(); }
24 void objectRemoved (ObjectType*)
override { sendChange(); }
25 void objectOrderChanged()
override { sendChange(); }
34 void handleAsyncUpdate()
override
36 sequence.updateTempoData();
58 return v.hasType (IDs::TEMPO);
64 t->incReferenceCount();
71 t->decReferenceCount();
91 return v.hasType (IDs::TIMESIG);
97 t->incReferenceCount();
104 t->decReferenceCount();
117 notifyListenersOfDeletion();
131 snap.savePreChangeState (
edit);
140 if (tempos->objects.isEmpty())
143 if (timeSigs->objects.isEmpty())
146 tempos->objects.getFirst()->startBeatNumber =
BeatPosition();
147 timeSigs->objects.getFirst()->startBeatNumber =
BeatPosition();
151 getTempo (i - 1)->startBeatNumber.get());
155 getTimeSig (i - 1)->startBeatNumber.get() + BeatDuration::fromBeats (1));
160 snap.remapEdit (
edit);
170 copyValueTree (state, other.state,
nullptr);
173void TempoSequence::freeResources()
194 return insertTempo (beatNum, bpm, curve, getUndoManager());
200 float defaultCurve = 1.0f;
203 return insertTempo (tracktion::roundToNearestBeat (
toBeats (time)), bpm, defaultCurve, um);
235 auto newTree = createValueTree (IDs::TIMESIG,
237 IDs::denominator, 4);
241 beatNum = tracktion::roundToNearestBeat (
toBeats (time));
244 index = state.indexOf (prev.state) + 1;
245 newTree = prev.state.createCopy();
248 if (prev.startBeatNumber == beatNum)
252 newTree.setProperty (IDs::startBeat, beatNum.
inBeats(),
nullptr);
254 state.addChild (newTree, index, um);
271 snap.savePreChangeState (
edit);
273 jassert (ts->state.isAChildOf (state));
274 state.removeChild (ts->state, getUndoManager());
277 snap.remapEdit (
edit);
287 snap.savePreChangeState (
edit);
291 if (range.contains (ts->getStartTime()))
295 snap.remapEdit (
edit);
304 jassert (ts->state.isAChildOf (state));
305 state.removeChild (ts->state, getUndoManager());
314 if (range.contains (ts->getPosition().getStart()))
327 const auto prevBeat = prev !=
nullptr ? prev->startBeatNumber :
BeatPosition();
328 const auto nextBeat = next !=
nullptr ? next->startBeatNumber : BeatPosition::fromBeats (0x7ffffff);
330 const auto newStart =
juce::jlimit (prevBeat, nextBeat, t->startBeatNumber.get() + deltaBeats);
331 t->set (snapToBeat ? tracktion::roundToNearestBeat (newStart) : newStart,
332 t->bpm, t->curve,
false);
346 const auto prevBeat = prev !=
nullptr ? prev->startBeatNumber :
BeatPosition();
347 const auto nextBeat = next !=
nullptr ? next->startBeatNumber : BeatPosition::fromBeats (0x7ffffff);
349 if (nextBeat < prevBeat + BeatDuration::fromBeats (2))
352 t->startBeatNumber.forceUpdateOfCachedValue();
353 const auto newBeat = t->startBeatNumber.get() + deltaBeats;
354 t->startBeatNumber =
juce::jlimit (prevBeat + BeatDuration::fromBeats (1), nextBeat - BeatDuration::fromBeats (1),
355 snapToBeat ? tracktion::roundToNearestBeat (newBeat) : newBeat);
363 time =
time - TimeDuration::fromSeconds (0.00001);
385 const auto beatRange =
toBeats (range);
390 const bool snapToBeat =
false;
391 const auto startTime =
toTime (beatRange.getStart());
392 const auto deltaBeats = -beatRange.getLength();
420 if (timeSigs->objects.getUnchecked (i)->startBeatNumber <= beat)
421 return *timeSigs->objects.getUnchecked (i);
423 jassert (timeSigs->size() > 0);
424 return *timeSigs->objects.getFirst();
430 if (timeSigs->objects.getUnchecked (i)->startTime <= t)
433 jassert (timeSigs->size() > 0);
439 return timeSigs->objects.indexOf (
const_cast<TimeSigSetting*
> (timeSigSetting));
451 if (tempos->objects.getUnchecked (i)->startBeatNumber <= beat)
452 return *tempos->objects.getUnchecked (i);
455 return *tempos->objects.getFirst();
461 if (tempos->objects.getUnchecked (i)->getStartTime() <= t)
471 if (tempos->objects.getUnchecked (i)->getStartTime() >= t)
479 return tempos->objects.indexOf (
const_cast<TempoSetting*
> (t));
484 updateTempoDataIfNeeded();
485 return internalSequence.getBpmAt (
time);
490 if (lengthOfOneBeatDependsOnTimeSignature)
492 updateTempoDataIfNeeded();
493 return internalSequence.getBeatsPerSecondAt (
time).v;
507 updateTempoDataIfNeeded();
508 return internalSequence.toBeats (
time);
513 return {
toBeats (range.getStart()),
524 updateTempoDataIfNeeded();
525 return internalSequence.toTime (beats);
530 return {
toTime (range.getStart()),
531 toTime (range.getEnd()) };
536 updateTempoDataIfNeeded();
537 return internalSequence.toTime (barsBeats);
542 updateTempoDataIfNeeded();
543 return internalSequence.toBarsAndBeats (t);
549 return internalSequence;
552void TempoSequence::updateTempoData()
554 tempos->cancelPendingUpdate();
565 tempoChanges.push_back ({ ts->startBeatNumber.get(), ts->bpm.get(), ts->curve.get() });
568 timeSigChanges.push_back ({ ts->startBeatNumber.get(), ts->numerator.get(), ts->denominator.get(), ts->triplets.get() });
570 for (
auto pc :
edit.pitchSequence.getPitches())
571 keyChanges.push_back ({ pc->startBeat.get(), { pc->pitch.get(),
static_cast<int> (pc->scale.get()) } });
575 tempo::Sequence newSeq (std::move (tempoChanges), std::move (timeSigChanges), std::move (keyChanges),
576 useDenominator ? tempo::LengthOfOneBeat::dependsOnTimeSignature
582 ts->startTime = newSeq.
toTime (ts->startBeatNumber.get());
585 ts->startTime = newSeq.
toTime (ts->startBeatNumber.get());
594 internalSequence = std::move (newSeq);
598void TempoSequence::updateTempoDataIfNeeded()
const
602 if (tempos->isUpdatePending())
603 tempos->handleUpdateNowIfNeeded();
605 if (timeSigs->isUpdatePending())
606 timeSigs->handleUpdateNowIfNeeded();
609void TempoSequence::handleAsyncUpdate()
617 return TRANS(
"Tempo Curve");
624 for (
auto t : tempos->objects)
625 if (range.contains (t->getStartTime()))
631HashCode TempoSequence::createHashForTemposInRange (TimeRange range)
const
635 for (
auto t : tempos->objects)
636 if (range.contains (t->getStartTime()))
637 hash ^= t->getHash();
643tempo::BarsAndBeats TempoSequence::timeToBarsBeats (
TimePosition t)
const
648TimePosition TempoSequence::barsBeatsToTime (tempo::BarsAndBeats barsBeats)
const
650 return toTime (barsBeats);
653BeatPosition TempoSequence::barsBeatsToBeats (tempo::BarsAndBeats barsBeats)
const
658BeatPosition TempoSequence::timeToBeats (TimePosition time)
const
663BeatRange TempoSequence::timeToBeats (TimeRange range)
const
668TimePosition TempoSequence::beatsToTime (BeatPosition beats)
const
673TimeRange TempoSequence::beatsToTime (BeatRange range)
const
678TimeSigSetting& TempoSequence::getTimeSigAtBeat (BeatPosition beat)
const
683TempoSetting& TempoSequence::getTempoAtBeat (BeatPosition beat)
const
698void EditTimecodeRemapperSnapshot::savePreChangeState (Edit& ed)
700 auto& tempoSequence = ed.tempoSequence;
704 auto addClip = [
this, &tempoSequence] (
auto clip)
706 auto pos = clip->getPosition();
710 cp.startBeat = tempoSequence.toBeats (pos.getStart());
711 cp.endBeat = tempoSequence.toBeats (pos.getEnd());
712 cp.contentStartBeat = toDuration (tempoSequence.toBeats (pos.getStartOfSource()));
718 for (
auto& c : t->getClips())
722 if (
auto cc =
dynamic_cast<ClipOwner*
> (c))
723 for (
auto childClip : cc->getClips())
727 if (
auto at =
dynamic_cast<AudioTrack*
> (t))
728 for (
auto slot : at->getClipSlotList().getClipSlots())
729 if (auto cc = slot->getClip())
740 for (
auto ap : aei->getAutomatableParameters())
742 if (! ap->automatableEditElement.remapOnTempoChange)
745 auto& curve = ap->getCurve();
746 auto numPoints = curve.getNumPoints();
754 for (
int p = 0; p < numPoints; ++p)
755 beats.
add (
toBeats (curve.getPoint (p).time, tempoSequence));
757 automation.
add ({ curve, std::move (beats) });
761 const auto loopRange = ed.getTransport().getLoopRange();
762 loopPositionBeats = { tempoSequence.toBeats (loopRange.getStart()),
763 tempoSequence.toBeats (loopRange.getEnd()) };
765 startPositionBeats = tempoSequence.toBeats (ed.getTransport().startPosition);
768void EditTimecodeRemapperSnapshot::remapEdit (Edit& ed)
770 auto& transport = ed.getTransport();
771 auto& tempoSequence = ed.tempoSequence;
772 tempoSequence.updateTempoData();
774 transport.startPosition = tempoSequence.toTime (startPositionBeats);
775 transport.setLoopRange (tempoSequence.toTime (loopPositionBeats));
777 for (
auto& cp : clips)
779 if (
auto c = cp.clip.get())
781 auto newStart = tempoSequence.toTime (cp.startBeat);
782 auto newEnd = tempoSequence.toTime (cp.endBeat);
783 auto newOffset = newStart - tempoSequence.toTime (toPosition (cp.contentStartBeat));
785 auto pos = c->getPosition();
787 if (std::abs ((pos.getStart() - newStart).inSeconds())
788 + std::abs ((pos.getEnd() - newEnd).inSeconds())
789 + std::abs ((pos.getOffset() - newOffset).inSeconds()) > 0.001)
796 auto ac =
dynamic_cast<AudioClipBase*
> (c);
798 if (ac !=
nullptr && ac->getAutoTempo())
799 c->setPosition ({ { newStart, newEnd }, newOffset });
801 c->setStart (newStart,
false,
true);
805 c->setPosition ({ { newStart, newEnd }, newOffset });
811 const ParameterChangeHandler::Disabler disabler (ed.getParameterChangeHandler());
813 for (
auto& a : automation)
814 for (
int i = a.beats.
size(); --i >= 0;)
815 a.curve.setPointTime (i, tempoSequence.toTime (a.beats.getUnchecked (i)));
818#if TRACKTION_UNIT_TESTS && ENGINE_UNIT_TESTS_TEMPO_SEQUENCE
825 TempoSequenceTests() :
juce::UnitTest (
"TempoSequence",
"Tracktion") {}
831 runModificationTests();
835 void expectBarsAndBeats (tempo::Sequence::Position& pos,
int bars,
int beats)
837 auto barsBeats = pos.getBarsBeats();
842 void expectTempoSetting (TempoSetting& tempo,
double startTime,
double bpm,
float curve)
849 void runPositionTests()
851 auto edit = Edit::createSingleTrackEdit (*Engine::getEngines().getFirst());
869 expectBarsAndBeats (pos, 0, 0);
871 expectBarsAndBeats (pos, 0, 1);
873 expectBarsAndBeats (pos, 0, 2);
875 expectBarsAndBeats (pos, 0, 3);
877 expectBarsAndBeats (pos, 1, 0);
880 expectBarsAndBeats (pos, 2, 0);
882 expectBarsAndBeats (pos, 2, 1);
884 expectBarsAndBeats (pos, 2, 2);
886 expectBarsAndBeats (pos, 2, 3);
895 expectBarsAndBeats (pos, -2, 0);
897 expectBarsAndBeats (pos, -2, 1);
899 expectBarsAndBeats (pos, -2, 2);
901 expectBarsAndBeats (pos, -2, 3);
903 expectBarsAndBeats (pos, -1, 0);
905 expectBarsAndBeats (pos, -1, 1);
907 expectBarsAndBeats (pos, -1, 2);
909 expectBarsAndBeats (pos, -1, 3);
911 expectBarsAndBeats (pos, 0, 0);
913 expectBarsAndBeats (pos, 0, 1);
915 expectBarsAndBeats (pos, 0, 2);
917 expectBarsAndBeats (pos, 0, 3);
919 expectBarsAndBeats (pos, 1, 0);
924 void runModificationTests()
927 auto edit = Edit::createSingleTrackEdit (*Engine::getEngines()[0]);
928 auto& ts = edit->tempoSequence;
935 ts.insertTempo (4_bp, 120, 0.0);
936 ts.insertTempo (4_bp, 300, 0.0);
937 ts.insertTempo (8_bp, 300, 0.0);
939 expectTempoSetting (*ts.getTempo (0), 0.0, 120.0, 1.0f);
940 expectTempoSetting (*ts.getTempo (1), 2.0, 120.0, 0.0f);
941 expectTempoSetting (*ts.getTempo (2), 2.0, 300.0, 0.0f);
942 expectTempoSetting (*ts.getTempo (3), 2.8, 300.0, 0.0f);
944 expectTempoSetting (ts.getTempoAt (0_tp), 0.0, 120.0, 1.0f);
945 expectTempoSetting (ts.getTempoAt (2.0s), 2.0, 300.0, 0.0f);
946 expectTempoSetting (ts.getTempoAt (3.0s), 2.8, 300.0, 0.0f);
951static TempoSequenceTests tempoSequenceTests;
void ensureStorageAllocated(int minNumElements)
void add(const ElementType &newElement)
void triggerAsyncUpdate()
void cancelPendingUpdate() noexcept
CriticalSection & getAudioCallbackLock() noexcept
void expectEquals(ValueType actual, ValueType expected, String failureMessage=String())
void beginTest(const String &testName)
void expectWithinAbsoluteError(ValueType actual, ValueType expected, ValueType maxAbsoluteError, String failureMessage=String())
int indexOf(const ValueTree &child) const noexcept
@ syncAbsolute
Sync to abslute time.
The Tracktion Edit class!
void sendTempoOrPitchSequenceChangedUpdates()
Sends a message to all the clips to let them know the tempo or pitch sequence has changed.
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.
Engine & engine
A reference to the Engine.
virtual bool lengthOfOneBeatDependsOnTimeSignature()
If this returns true, it means that the length (in seconds) of one "beat" at any point in an edit is ...
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
Holds a list of TempoSetting objects, to form a sequence of tempo changes.
TimePosition toTime(BeatPosition) const
Converts a number of beats a time.
Edit & getEdit() const
Returns the Edit this TempoSequence refers to.
void moveTempoStart(int index, BeatDuration deltaBeats, bool snapToBeat)
Moves the TempoSetting at a given index by a number of beats.
void removeTimeSig(int index)
Removes the TimeSigSetting at a given index.
void insertSpaceIntoSequence(TimePosition time, TimeDuration amountOfSpaceInSeconds, bool snapToBeat)
Inserts space in to a sequence, shifting TempoSettings and TimeSigs.
void deleteRegion(TimeRange)
Removes a region in a sequence, shifting TempoSettings and TimeSigs.
void moveTimeSigStart(int index, BeatDuration deltaBeats, bool snapToBeat)
Moves the TimeSigSetting at a given index by a number of beats.
void createEmptyState()
Resets this to a default, empty state.
TimeSigSetting & getTimeSigAt(TimePosition) const
Returns the TimeSigSetting at a given position.
void removeTimeSigsBetween(TimeRange)
Removes any TimeSigSetting[s] within the range.
TempoSetting::Ptr insertTempo(TimePosition)
Inserts a tempo break that can be edited later.
const juce::Array< TimeSigSetting * > & getTimeSigs() const
Returns an array of the TimeSigSetting.
int countTemposInRegion(TimeRange) const
Returns the number of TempoSetting[s] in the given range.
int indexOfTempo(const TempoSetting *) const
Returns the index of the given TempoSetting.
int getNumTimeSigs() const
Returns the number of TimeSigSetting[s] in the sequence.
~TempoSequence() override
Destructor.
bool isTripletsAtTime(TimePosition) const
Returns true if the TempoSetting is triplets at the given time.
int indexOfTimeSig(const TimeSigSetting *) const
Returns the index of a given TimeSigSetting.
juce::String getSelectableDescription() override
Subclasses must return a description of what they are.
BeatPosition toBeats(TimePosition) const
Converts a time to a number of beats.
int getNumTempos() const
Returns the current number of TempoSettings.
void copyFrom(const TempoSequence &)
Copies the contents of another TempoSequence.
const juce::Array< TempoSetting * > & getTempos() const
Returns the TempoSettings.
tempo::BarsAndBeats toBarsAndBeats(TimePosition) const
Converts a time to a number of BarsAndBeats.
const tempo::Sequence & getInternalSequence() const
N.B.
void removeTemposBetween(TimeRange, bool remapEdit)
Removes any TempoSetting[s] within the range.
TimeSigSetting * getTimeSig(int index) const
Returns the TimeSigSetting at a given index.
void removeTempo(int index, bool remapEdit)
Removes the TempoSetting at a given index.
TempoSetting * getTempo(int index) const
Returns the TempoSetting at the given index.
int indexOfTimeSigAt(TimePosition) const
Returns the index of TimeSigSetting at a given position.
double getBeatsPerSecondAt(TimePosition, bool lengthOfOneBeatDependsOnTimeSignature=false) const
Returns the tempo at a given position.
TempoSetting & getTempoAt(TimePosition) const
Returns the TempoSetting at the given position.
void setState(const juce::ValueTree &, bool remapEdit)
Sets the state this TempoSequence should refer to.
Edit & edit
The Edit this sequence belongs to.
int indexOfNextTempoAt(TimePosition) const
Returns the index of the TempoSetting after the given position.
int indexOfTempoAt(TimePosition) const
Returns the index of the TempoSetting at the given position.
TempoSequence(Edit &)
Creates a TempoSequence for an Edit.
double getBpmAt(TimePosition) const
Returns the tempo at a given position.
TimeSigSetting::Ptr insertTimeSig(TimePosition)
Inserts a new TimeSigSetting at the given position.
A tempo value, as used in a TempoSequence.
static juce::ValueTree create(BeatPosition startBeat, double bpm, float curve)
Creates a tree to prepresent a TempoSetting.
#define TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
tempo::Sequence::Position createPosition(const TempoSequence &s)
Creates a Position to iterate over the given TempoSequence.
juce::Array< ClipTrack * > getClipTracks(const Edit &edit)
Returns all the ClipTracks in an Edit.
BeatPosition toBeats(TimePosition tp, const TempoSequence &ts)
Converts a TimePosition to a BeatPosition given a TempoSequence.
juce::Array< AutomatableEditItem * > getAllAutomatableEditItems(const Edit &edit)
Returns all AutomatableEditItems in an Edit.
RangeType< BeatPosition > BeatRange
A RangeType based on beats.
RangeType< TimePosition > TimeRange
A RangeType based on real time (i.e.
Represents a duration in beats.
Represents a position in beats.
constexpr double inBeats() const
Returns the position as a number of beats.
Represents a duration in real-life time.
constexpr double inSeconds() const
Returns the TimeDuration as a number of seconds.
Represents a position in real-life time.
A Sequence::Position is an iterator through a Sequence.
Takes a copy of all the beat related things in an edit in terms of bars/beats and then remaps these a...
LengthOfOneBeat
Used to determine the length of a beat in beat <-> time conversions.
@ isAlwaysACrotchet
Signifies the length of one beat always depends only the current BPM at that point in the edit,...