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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_PitchSequence.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14struct PitchSequence::PitchList : public ValueTreeObjectList<PitchSetting>,
15 private juce::AsyncUpdater
16{
17 PitchList (PitchSequence& s, const juce::ValueTree& parentTree)
18 : ValueTreeObjectList<PitchSetting> (parentTree), pitchSequence (s)
19 {
20 rebuildObjects();
21 }
22
23 ~PitchList() override
24 {
25 freeObjects();
26 }
27
28 bool isSuitableType (const juce::ValueTree& v) const override
29 {
30 return v.hasType (IDs::PITCH);
31 }
32
33 PitchSetting* createNewObject (const juce::ValueTree& v) override
34 {
35 auto t = new PitchSetting (pitchSequence.getEdit(), v);
36 t->incReferenceCount();
37 return t;
38 }
39
40 void deleteObject (PitchSetting* t) override
41 {
42 jassert (t != nullptr);
43 t->decReferenceCount();
44 }
45
46 void newObjectAdded (PitchSetting*) override { sendChange(); }
47 void objectRemoved (PitchSetting*) override { sendChange(); }
48 void objectOrderChanged() override { sendChange(); }
49 void valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier&) override { sendChange(); }
50
51 void sendChange()
52 {
53 if (! pitchSequence.getEdit().isLoading())
55 }
56
57 void handleAsyncUpdate() override
58 {
59 pitchSequence.getEdit().tempoSequence.updateTempoData();
60 }
61
62 PitchSequence& pitchSequence;
63};
64
65//==============================================================================
66PitchSequence::PitchSequence() {}
67PitchSequence::~PitchSequence() {}
68
69Edit& PitchSequence::getEdit() const
70{
71 jassert (edit != nullptr);
72 return *edit;
73}
74
75juce::UndoManager* PitchSequence::getUndoManager() const
76{
77 return &getEdit().getUndoManager();
78}
79
80void PitchSequence::clear()
81{
82 if (auto first = getPitch (0))
83 {
84 auto pitch = first->getPitch();
85 state.removeAllChildren (getUndoManager());
86 insertPitch ({}, pitch);
87 }
88 else
89 {
91 }
92}
93
94void PitchSequence::initialise (Edit& ed, const juce::ValueTree& v)
95{
96 edit = &ed;
97 state = v;
98
99 list = std::make_unique<PitchList> (*this, state);
100
101 if (getNumPitches() == 0)
102 insertPitch ({}, 60);
103
104 sortEvents();
105}
106
107void PitchSequence::freeResources()
108{
109 list.reset();
110}
111
112void PitchSequence::copyFrom (const PitchSequence& other)
113{
114 copyValueTree (state, other.state, nullptr);
115}
116
117const juce::Array<PitchSetting*>& PitchSequence::getPitches() const { return list->objects; }
118int PitchSequence::getNumPitches() const { return list->objects.size(); }
119PitchSetting* PitchSequence::getPitch (int index) const { return list->objects[index]; }
120PitchSetting& PitchSequence::getPitchAt (TimePosition time) const { return *list->objects[indexOfPitchAt (time)]; }
121
122PitchSetting& PitchSequence::getPitchAtBeat (BeatPosition beat) const
123{
124 for (int i = list->objects.size(); --i > 0;)
125 {
126 auto p = list->objects.getUnchecked (i);
127
128 if (p->getStartBeatNumber() <= beat)
129 return *p;
130 }
131
132 jassert (list->size() > 0);
133 return *list->objects.getFirst();
134}
135
136int PitchSequence::indexOfPitchAt (TimePosition t) const
137{
138 for (int i = list->objects.size(); --i > 0;)
139 if (list->objects.getUnchecked (i)->getPosition().getStart() <= t)
140 return i;
141
142 jassert (list->size() > 0);
143 return 0;
144}
145
146int PitchSequence::indexOfNextPitchAt (TimePosition t) const
147{
148 for (int i = 0; i < list->objects.size(); ++i)
149 if (list->objects.getUnchecked (i)->getPosition().getStart() >= t)
150 return i;
151
152 jassert (list->size() > 0);
153 return list->objects.size();
154}
155
156int PitchSequence::indexOfPitch (const PitchSetting* pitchSetting) const
157{
158 return list->objects.indexOf ((PitchSetting*) pitchSetting);
159}
160
161PitchSetting::Ptr PitchSequence::insertPitch (TimePosition time)
162{
163 return insertPitch (edit->tempoSequence.toBeats (time), -1);
164}
165
166PitchSetting::Ptr PitchSequence::insertPitch (BeatPosition beatNum, int pitch)
167{
168 int newIndex = -1;
169
170 if (list->size() > 0)
171 {
172 auto& prev = getPitchAtBeat (beatNum);
173
174 // don't add in the same place as another one
175 if (prev.getStartBeatNumber() == beatNum)
176 {
177 if (pitch >= 0)
178 prev.setPitch (pitch);
179
180 return prev;
181 }
182
183 if (pitch < 0)
184 pitch = prev.getPitch();
185
186 newIndex = indexOfPitch (&prev) + 1;
187 }
188
189 auto v = createValueTree (IDs::PITCH,
190 IDs::startBeat, beatNum.inBeats(),
191 IDs::pitch, pitch);
192
193 if (newIndex < 0)
194 newIndex = list->objects.size();
195
196 state.addChild (v, newIndex, getUndoManager());
197
198 jassert (list->objects[newIndex]->state == v);
199 return list->objects[newIndex];
200}
201
202void PitchSequence::movePitchStart (PitchSetting& p, BeatDuration deltaBeats, bool snapToBeat)
203{
204 auto index = indexOfPitch (&p);
205
206 if (index > 0 && deltaBeats != BeatDuration())
207 {
208 if (auto t = getPitch (index))
209 {
210 t->startBeat.forceUpdateOfCachedValue();
211 auto newBeat = t->getStartBeat() + deltaBeats;
212 t->setStartBeat (BeatPosition::fromBeats (juce::jlimit (0.0, 1e10, snapToBeat ? juce::roundToInt (newBeat.inBeats())
213 : newBeat.inBeats())));
214 }
215 }
216}
217
218void PitchSequence::insertSpaceIntoSequence (TimePosition time, TimeDuration amountOfSpaceInSeconds, bool snapToBeat)
219{
220 // there may be a tempo change at this time so we need to find the tempo to the left of it hence the nudge
221 time = time - TimeDuration::fromSeconds (0.00001);
222 auto beatsToInsert = getEdit().tempoSequence.getBeatsPerSecondAt (time) * amountOfSpaceInSeconds.inSeconds();
223 auto endIndex = indexOfNextPitchAt (time);
224
225 for (int i = getNumPitches(); --i >= endIndex;)
226 movePitchStart (*getPitch (i), BeatDuration::fromBeats (beatsToInsert), snapToBeat);
227}
228
229void PitchSequence::sortEvents()
230{
231 struct PitchSorter
232 {
233 static int compareElements (const juce::ValueTree& p1, const juce::ValueTree& p2) noexcept
234 {
235 const double beat1 = p1[IDs::startBeat];
236 const double beat2 = p2[IDs::startBeat];
237
238 return beat1 < beat2 ? -1 : (beat1 > beat2 ? 1 : 0);
239 }
240 };
241
242 PitchSorter sorter;
243 state.sort (sorter, getUndoManager(), true);
244}
245
246}} // namespace tracktion { inline namespace engine
void removeAllChildren(UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
void sort(ElementComparator &comparator, UndoManager *undoManager, bool retainOrderOfEquivalentItems)
The Tracktion Edit class!
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.
void insertSpaceIntoSequence(TimePosition, TimeDuration amountOfSpace, bool snapToBeat)
Inserts space in to a sequence, shifting all PitchSettings.
BeatPosition toBeats(TimePosition) const
Converts a time to a number of beats.
double getBeatsPerSecondAt(TimePosition, bool lengthOfOneBeatDependsOnTimeSignature=false) const
Returns the tempo at a given position.
T is_pointer_v
#define jassert(expression)
#define jassertfalse
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
T prev(T... args)
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.
time