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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_AutomationRecordManager.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
14AutomationRecordManager::AutomationRecordManager (Edit& ed)
15 : engine (ed.engine), edit (ed)
16{
17 if (edit.shouldPlay())
18 {
20
23 else if (auto t = juce::Thread::getCurrentThread())
25 else
26 jassert (juce::MessageManager::getInstance()->isThisTheMessageThread());
27
28 if (mml == nullptr || mml->lockWasGained())
29 edit.getTransport().addChangeListener (this);
30 else
32 }
33
34
35 readingAutomation.referTo (edit.getTransport().state, IDs::automationRead, nullptr, true);
36}
37
38AutomationRecordManager::~AutomationRecordManager()
39{
40 if (edit.shouldPlay())
41 edit.getTransport().removeChangeListener (this);
42}
43
44void AutomationRecordManager::setReadingAutomation (bool b)
45{
46 if (readingAutomation != b)
47 {
48 readingAutomation = b;
49 sendChangeMessage();
50
51 engine.getExternalControllerManager().automationModeChanged (readingAutomation, writingAutomation);
52 }
53}
54
55void AutomationRecordManager::setWritingAutomation (bool b)
56{
57 if (writingAutomation != b)
58 {
59 writingAutomation = b;
60 sendChangeMessage();
61
62 engine.getExternalControllerManager().automationModeChanged (readingAutomation, writingAutomation);
63 }
64}
65
66TimeDuration AutomationRecordManager::getGlideSeconds (Engine& e)
67{
68 return TimeDuration::fromSeconds (static_cast<double> (e.getPropertyStorage().getProperty (SettingID::glideLength)));
69}
70
71void AutomationRecordManager::setGlideSeconds (Engine& e, TimeDuration secs)
72{
73 e.getPropertyStorage().setProperty (SettingID::glideLength, secs.inSeconds());
74}
75
76void AutomationRecordManager::changeListenerCallback (ChangeBroadcaster* source)
77{
78 // called back when transport state changes
79 if (source == &edit.getTransport())
80 {
81 const bool isPlaying = edit.getTransport().isPlaying()
82 || edit.getTransport().isRecording();
83
84 if (wasPlaying != isPlaying)
85 {
86 wasPlaying = isPlaying;
87
88 if (! isPlaying)
89 punchOut (false);
90
91 jassert (recordedParams.isEmpty());
92 const juce::ScopedLock sl (lock);
93 recordedParams.clear();
94 }
95 }
96}
97
98bool AutomationRecordManager::isRecordingAutomation() const
99{
100 const juce::ScopedLock sl (lock);
101 return recordedParams.size() > 0;
102}
103
104bool AutomationRecordManager::isParameterRecording (AutomatableParameter* param) const
105{
106 const juce::ScopedLock sl (lock);
107
108 for (auto p : recordedParams)
109 if (&p->parameter == param)
110 return true;
111
112 return false;
113}
114
115void AutomationRecordManager::punchOut (bool toEnd)
116{
117 const juce::ScopedLock sl (lock);
118
119 if (recordedParams.size() > 0)
120 {
121 auto endTime = edit.getTransport().getPosition();
122
123 // If the punch out was triggered by a position change, we want to make
124 // sure the playhead position is used as the end time
125 if (auto epc = edit.getTransport().getCurrentPlaybackContext())
126 {
127 endTime = epc->isLooping() ? std::max (epc->getUnloopedPosition(),
128 epc->getLoopTimes().getEnd())
129 : epc->getPosition();
130 }
131
132 for (auto param : recordedParams)
133 {
134 if (toEnd)
135 endTime = std::max (endTime, toPosition (param->parameter.getCurve().getLength()) + TimeDuration::fromSeconds (1.0));
136
137 applyChangesToParameter (param, endTime, toEnd);
138 param->parameter.resetRecordingStatus();
139 }
140
141 recordedParams.clear();
142 sendChangeMessage();
143 }
144}
145
146void AutomationRecordManager::applyChangesToParameter (AutomationParamData* parameter, TimePosition end, bool toEnd)
147{
150
151 {
152 std::unique_ptr<AutomationCurve> curve (new AutomationCurve());
153 curve->setOwnerParameter (&parameter->parameter);
154
155 for (int i = 0; i < parameter->changes.size(); ++i)
156 {
157 auto& change = parameter->changes.getReference (i);
158
159 if (i > 0 && change.time < parameter->changes.getReference (i - 1).time - TimeDuration::fromSeconds (0.1))
160 {
161 // gone back to the start - must be looping, so add this to the list and carry on..
162 if (curve->getNumPoints() > 0)
163 {
164 newCurves.add (curve.release());
166 curve->setOwnerParameter (&parameter->parameter);
167 }
168 }
169
170 const float oldVal = (i == 0) ? (parameter->parameter.getCurve().getNumPoints() > 0 ? parameter->parameter.getCurve().getValueAt (change.time)
171 : parameter->originalValue)
172 : curve->getValueAt (change.time);
173
174 const float newVal = parameter->parameter.snapToState (change.value);
175
176 if (parameter->parameter.isDiscrete())
177 {
178 curve->addPoint (change.time, oldVal, 0.0f);
179 curve->addPoint (change.time, newVal, 0.0f);
180 }
181 else
182 {
183 if (std::abs (oldVal - newVal) > (parameter->parameter.getValueRange().getLength() * 0.21f))
184 {
185 if (i == 0)
186 curve->addPoint (change.time - TimeDuration::fromSeconds (0.000001), oldVal, 0.0f);
187 else
188 curve->addPoint (change.time, oldVal, 0.0f);
189 }
190
191 curve->addPoint (change.time, newVal, 0.0f);
192 }
193 }
194
195 if (curve->getNumPoints() > 0)
196 newCurves.add (curve.release());
197 }
198
199 auto glideLength = getGlideSeconds (engine);
200
201 for (int i = 0; i < newCurves.size(); ++i)
202 {
203 auto& curve = *newCurves.getUnchecked (i);
204
205 if (curve.getNumPoints() > 0)
206 {
207 auto startTime = curve.getPointTime (0);
208 auto endTime = end;
209
210 if (i < newCurves.size() - 1)
211 endTime = curve.getPointTime (curve.getNumPoints() - 1);
212
213 // if the curve is empty, set the parameter so that the bits outside the new curve
214 // are set to the levels they were at when we started recording..
215 if (parameter->parameter.getCurve().getNumPoints() == 0)
216 parameter->parameter.setParameter (parameter->originalValue, juce::sendNotification);
217
218 auto& c = parameter->parameter.getCurve();
219 TimeRange curveRange (startTime, endTime + glideLength);
220 c.mergeOtherCurve (curve, curveRange,
221 startTime, glideLength, false, toEnd);
222
223 if (engine.getPropertyStorage().getProperty (SettingID::simplifyAfterRecording, true))
224 c.simplify (curveRange.expanded (TimeDuration::fromSeconds (0.001)), 0.01, 0.002f);
225 }
226 }
227}
228
229void AutomationRecordManager::toggleWriteAutomationMode()
230{
231 punchOut (false);
232
233 setWritingAutomation (! isWritingAutomation());
234}
235
236void AutomationRecordManager::postFirstAutomationChange (AutomatableParameter& param, float originalValue)
237{
238 TRACKTION_ASSERT_MESSAGE_THREAD
239 auto entry = std::make_unique<AutomationParamData> (param, originalValue);
240
241 // recording status has changed, so inform our listeners
242 sendChangeMessage();
243
244 const juce::ScopedLock sl (lock);
245 recordedParams.add (entry.release());
246}
247
248void AutomationRecordManager::postAutomationChange (AutomatableParameter& param, TimePosition time, float value)
249{
250 TRACKTION_ASSERT_MESSAGE_THREAD
251 const juce::ScopedLock sl (lock);
252
253 for (auto p : recordedParams)
254 {
255 if (&p->parameter == &param)
256 {
257 p->changes.add (AutomationParamData::Change (time, value));
258 break;
259 }
260 }
261}
262
263void AutomationRecordManager::parameterBeingDeleted (AutomatableParameter& param)
264{
265 const juce::ScopedLock sl (lock);
266
267 for (int i = recordedParams.size(); --i >= 0;)
268 if (&recordedParams.getUnchecked (i)->parameter == &param)
269 recordedParams.remove (i);
270}
271
272}} // namespace tracktion { inline namespace engine
static ThreadPoolJob * getCurrentThreadPoolJob()
The Engine is the central class for all tracktion sessions.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
T end(T... args)
T is_pointer_v
#define jassert(expression)
#define jassertfalse
T max(T... args)
sendNotification
RangeType< TimePosition > TimeRange
A RangeType based on real time (i.e.
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
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.