Skip to content

Commit 5c850bc

Browse files
authored
Automation: Refactored AutomationCurve and added automation clips via AutomationCurveModifier (#258)
1 parent 0a5f4e6 commit 5c850bc

File tree

58 files changed

+5772
-1311
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+5772
-1311
lines changed

BREAKING-CHANGES.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
# Tracktion Engine breaking changes
22

3-
## Develop
3+
4+
### Change
5+
`AutomationCurve` has been restructured. It now only stores the parameter as a string and not a reference.
6+
7+
#### Possible Issues
8+
Many functions now need a `AutomatableParameter&`, `TempoSequence&` or `juce::UndoManager*` as additional arguments.
9+
10+
#### Workaround
11+
These extra parameters are usually within easy reach at the call site. You can use the new `getTempoSequence (Type&)` and `getUndoManager_p (Type&)` to easily get these from a bunch of objects.
12+
13+
#### Rationale
14+
This decoupling enables `AutomationCurve` to be used in more places and supports clip automation and `AutomationClip`s.
15+
16+
___
417

518
### Change
619
The `Edit` constructor can now throw exceptions in rare cases. E.g. if it's being constructed on the message thread which is blocked.

modules/juce

Submodule juce updated 107 files

modules/tracktion_core/tracktion_TestConfig.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define GRAPH_UNIT_TESTS_EDITNODE 1
2020

2121
#define ENGINE_UNIT_TESTS_AUTOMATION 1
22+
#define ENGINE_UNIT_TESTS_AUTOMATION_CURVE_LIST 1
2223
#define ENGINE_UNIT_TESTS_AUX_SEND 1
2324
#define ENGINE_UNIT_TESTS_CLIPBOARD 1
2425
#define ENGINE_UNIT_TESTS_CLIPSLOT 1
@@ -73,6 +74,7 @@
7374

7475
#define GRAPH_BENCHMARKS_THREADS 1
7576

77+
#define ENGINE_BENCHMARKS_AUTOMATIONITERATOR 1
7678
#define ENGINE_BENCHMARKS_AUDIOFILECACHE 1
7779
#define ENGINE_BENCHMARKS_CONTAINERCLIP 1
7880
#define ENGINE_BENCHMARKS_MIDICLIP 1

modules/tracktion_core/utilities/tracktion_AlgorithmAdapters.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,17 @@ juce::Array<Type> remove_if_nullptr (juce::Array<Type>&& container)
173173
return std::move (container);
174174
}
175175

176+
/** Implemtation of C++23's std::unreachable. */
177+
[[noreturn]] inline void unreachable()
178+
{
179+
// Uses compiler specific extensions if possible.
180+
// Even if no extension is used, undefined behavior is still raised by
181+
// an empty function body and the noreturn attribute.
182+
#if defined(_MSC_VER) && !defined(__clang__) // MSVC
183+
__assume(false);
184+
#else // GCC, Clang
185+
__builtin_unreachable();
186+
#endif
187+
}
188+
176189
}}

modules/tracktion_core/utilities/tracktion_Benchmark.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,29 @@ struct ScopedBenchmark
177177
Benchmark benchmark;
178178
};
179179

180+
//==============================================================================
181+
/**
182+
Similar to ScopedBenchmark except it doesn't start/stop the measurement so
183+
you can do this manually (or with a ScopedMeasurement) around a specific
184+
code section.
185+
*/
186+
struct PublishingBenchmark
187+
{
188+
/** Constructs a Benchmark. */
189+
PublishingBenchmark (BenchmarkDescription desc)
190+
: benchmark (std::move (desc))
191+
{
192+
}
193+
194+
/** Adds the result to the BenchmarkList. */
195+
~PublishingBenchmark()
196+
{
197+
BenchmarkList::getInstance().addResult (benchmark.getResult());
198+
}
199+
200+
Benchmark benchmark;
201+
};
202+
180203
//==============================================================================
181204
/**
182205
Helper class for starting/stopping a benchmark measurement.

modules/tracktion_core/utilities/tracktion_Bezier.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,39 @@ inline void getBezierEnds (const double x1, const double y1, const double x2, co
7070
}
7171
}
7272

73+
struct BezierEnds
74+
{
75+
double x1, y1, x2, y2;
76+
};
77+
78+
inline BezierEnds getBezierEnds (const double x1, const double y1, const double x2, const double y2, const double c) noexcept
79+
{
80+
BezierEnds ends;
81+
82+
auto minic = (std::abs (c) - 0.5f) * 2.0f;
83+
auto run = minic * (x2 - x1);
84+
auto rise = minic * ((y2 > y1) ? (y2 - y1) : (y1 - y2));
85+
86+
if (c > 0)
87+
{
88+
ends.x1 = x1 + run;
89+
ends.y1 = (float) y1;
90+
91+
ends.x2 = x2;
92+
ends.y2 = (float) (y1 < y2 ? (y2 - rise) : (y2 + rise));
93+
}
94+
else
95+
{
96+
ends.x1 = x1;
97+
ends.y1 = (float) (y1 < y2 ? (y1 + rise) : (y1 - rise));
98+
99+
ends.x2 = x2 - run;
100+
ends.y2 = (float) y2;
101+
}
102+
103+
return ends;
104+
}
105+
73106
inline double getBezierYFromX (double x, double x1, double y1, double xb, double yb, double x2, double y2) noexcept
74107
{
75108
// test for straight lines and bail out
@@ -110,4 +143,13 @@ inline double getBezierYFromX (double x, double x1, double y1, double xb, double
110143
return y;
111144
}
112145

146+
inline double getBezierXfromT (double t, double x1, double xb, double x2)
147+
{
148+
// test for straight lines and bail out
149+
if (x1 == x2)
150+
return (x1 + x2) / 2.0 * t + x1;
151+
152+
return (std::pow (1.0 - t, 2.0) * x1) + 2 * t * (1 - t) * xb + std::pow (t, 2.0) * x2;
153+
}
154+
113155
}}

modules/tracktion_engine/model/automation/tracktion_AutomatableEditItem.cpp

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ AutomatableEditItem::AutomatableEditItem (Edit& ed, const juce::ValueTree& v)
1616
elementState (v)
1717
{
1818
remapOnTempoChange.referTo (elementState, IDs::remapOnTempoChange, &edit.getUndoManager(), false);
19+
edit.automatableEditItemCache.addItem (*this);
1920
}
2021

2122
AutomatableEditItem::~AutomatableEditItem()
2223
{
24+
edit.automatableEditItemCache.removeItem (*this);
2325
}
2426

2527
//==============================================================================
@@ -106,7 +108,6 @@ AutomatableParameterTree& AutomatableEditItem::getParameterTree() const
106108
sendListChangeMessage();
107109
}
108110

109-
110111
return parameterTree;
111112
}
112113

@@ -128,6 +129,11 @@ juce::ReferenceCountedArray<AutomatableParameter> AutomatableEditItem::getFlatte
128129
}
129130

130131
//==============================================================================
132+
bool AutomatableEditItem::isAutomationNeeded() const noexcept
133+
{
134+
return numActiveParameters.load (std::memory_order_acquire) > 0;
135+
}
136+
131137
void AutomatableEditItem::setAutomatableParamPosition (TimePosition time)
132138
{
133139
if (setIfDifferent (lastTime, time))
@@ -142,6 +148,9 @@ bool AutomatableEditItem::isBeingActivelyPlayed() const
142148

143149
void AutomatableEditItem::updateAutomatableParamPosition (TimePosition time)
144150
{
151+
if (! isAutomationNeeded())
152+
return;
153+
145154
for (auto p : automatableParams)
146155
if (p->isAutomationActive())
147156
p->updateToFollowCurve (time);
@@ -161,6 +170,79 @@ void AutomatableEditItem::resetRecordingStatus()
161170
p->resetRecordingStatus();
162171
}
163172

173+
void AutomatableEditItem::updateStreamIterators()
174+
{
175+
for (auto p : automatableParams)
176+
p->updateStream();
177+
}
178+
179+
void AutomatableEditItem::addActiveParameter (const AutomatableParameter& p)
180+
{
181+
if (auto param = automatableParams[automatableParams.indexOf (&p)])
182+
{
183+
// This confusing order is to avoid allocating whilst the lock is held
184+
juce::ReferenceCountedArray<AutomatableParameter> newParams;
185+
int numParams = 0;
186+
187+
{
188+
const std::scoped_lock sl (activeParameterLock);
189+
190+
if (activeParameters.contains (param))
191+
return;
192+
193+
numParams = activeParameters.size() + 1;
194+
}
195+
196+
newParams.ensureStorageAllocated (numParams);
197+
198+
{
199+
const std::scoped_lock sl (activeParameterLock);
200+
newParams = activeParameters;
201+
}
202+
203+
newParams.add (param);
204+
205+
const std::scoped_lock sl (activeParameterLock);
206+
std::swap (activeParameters, newParams);
207+
}
208+
209+
lastTime = -1.0s;
210+
}
211+
212+
void AutomatableEditItem::removeActiveParameter (const AutomatableParameter& p)
213+
{
214+
if (auto param = automatableParams[automatableParams.indexOf (&p)])
215+
{
216+
// This confusing order is to avoid removing whilst the lock is held,
217+
// juce::ReferenceCountedArray can free when that happens
218+
juce::ReferenceCountedArray<AutomatableParameter> nowActiveParams;
219+
int numParams = 0;
220+
221+
{
222+
const std::scoped_lock sl (activeParameterLock);
223+
224+
if (! activeParameters.contains (param))
225+
return;
226+
227+
numParams = activeParameters.size() - 1;
228+
}
229+
230+
nowActiveParams.ensureStorageAllocated (numParams);
231+
232+
{
233+
const std::scoped_lock sl (activeParameterLock);
234+
nowActiveParams = activeParameters;
235+
}
236+
237+
nowActiveParams.removeObject (param);
238+
239+
const std::scoped_lock sl (activeParameterLock);
240+
std::swap (activeParameters, nowActiveParams);
241+
}
242+
243+
lastTime = -1.0s;
244+
}
245+
164246
//==============================================================================
165247
void AutomatableEditItem::buildParameterTree() const
166248
{
@@ -183,6 +265,10 @@ void AutomatableEditItem::addAutomatableParameter (const AutomatableParameter::P
183265
{
184266
jassert (param != nullptr);
185267
automatableParams.add (param);
268+
269+
if (param->isAutomationActive())
270+
addActiveParameter (*param);
271+
186272
rebuildParameterTree();
187273
}
188274

@@ -207,22 +293,10 @@ juce::ReferenceCountedArray<AutomatableParameter> AutomatableEditItem::getFlatte
207293
return params;
208294
}
209295

210-
void AutomatableEditItem::updateActiveParameters()
296+
bool AutomatableEditItem::isActiveParameter (AutomatableParameter& p)
211297
{
212-
CRASH_TRACER
213-
juce::ReferenceCountedArray<AutomatableParameter> nowActiveParams;
214-
215-
for (auto ap : automatableParams)
216-
if (ap->isAutomationActive())
217-
nowActiveParams.add (ap);
218-
219-
{
220-
const std::scoped_lock sl (activeParameterLock);
221-
activeParameters.swapWith (nowActiveParams);
222-
automationActive.store (! activeParameters.isEmpty(), std::memory_order_relaxed);
223-
}
224-
225-
lastTime = -1.0s;
298+
const std::scoped_lock sl (activeParameterLock);
299+
return activeParameters.contains (p);
226300
}
227301

228302
void AutomatableEditItem::saveChangedParametersToState()

modules/tracktion_engine/model/automation/tracktion_AutomatableEditItem.h

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,32 +50,42 @@ class AutomatableEditItem : public EditItem
5050

5151
//==============================================================================
5252
// true if it's got any points on any params
53-
bool isAutomationNeeded() const noexcept { return automationActive.load (std::memory_order_relaxed); }
53+
bool isAutomationNeeded() const noexcept;
5454

5555
// updates any automatables to their state at this time
5656
void setAutomatableParamPosition (TimePosition);
5757

5858
// true if it's not been more than a few hundred ms since a block was processed
5959
bool isBeingActivelyPlayed() const;
6060

61-
/** Updates all the auto params to their positions at this time. */
61+
/** Updates all the auto params to their positions at this time.
62+
[[ message_thread ]]
63+
*/
6264
virtual void updateAutomatableParamPosition (TimePosition);
6365

6466
/** Updates all the parameter streams to their positions at this time.
65-
This should be used during real time processing as it's a lot quicker than the above method.
67+
This should be used during real time processing as it's a lot quicker than
68+
the above method but is called automatically by the audio graph so
69+
shouldn't really be called manually.
6670
*/
6771
void updateParameterStreams (TimePosition);
6872

69-
/** Iterates all the parameters to find out which ones need to be automated. */
70-
void updateActiveParameters();
71-
7273
/** Marks the end of an automation recording stream. Call this when play stops or starts. */
7374
void resetRecordingStatus();
7475

7576
//==============================================================================
7677
juce::ValueTree elementState;
7778
juce::CachedValue<bool> remapOnTempoChange;
7879

80+
/** @internal. */
81+
void updateStreamIterators();
82+
/** @internal. */
83+
void addActiveParameter (const AutomatableParameter&);
84+
/** @internal. */
85+
void removeActiveParameter (const AutomatableParameter&);
86+
/** @internal. Testing only. */
87+
bool isActiveParameter (AutomatableParameter&);
88+
7989
protected:
8090
virtual void buildParameterTree() const;
8191

@@ -96,7 +106,8 @@ class AutomatableEditItem : public EditItem
96106
mutable AutomatableParameterTree parameterTree;
97107

98108
mutable bool parameterTreeBuilt = false;
99-
std::atomic<bool> automationActive { false };
109+
friend struct AutomatableParameter::ScopedActiveParameter;
110+
mutable std::atomic<int> numActiveParameters { 0 };
100111
uint32_t systemTimeOfLastPlayedBlock = 0;
101112
TimePosition lastTime;
102113

0 commit comments

Comments
 (0)