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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_PlayHead.h
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
12
13namespace tracktion { inline namespace engine
14{
15
17{
18public:
19 PlayHead() = default;
20
21 //==============================================================================
22 void setPosition (double newTime)
23 {
24 if (newTime != getPosition())
25 userInteraction();
26
27 overridePosition (newTime);
28 }
29
30 void play (legacy::EditTimeRange rangeToPlay, bool looped)
31 {
32 const juce::ScopedLock sl (lock);
33
34 playRange = rangeToPlay;
35 setPosition (playRange.start);
36 speed = 1.0;
37 looping = looped && (playRange.getLength() > 0.001);
38 }
39
40 // carries on from the last position
41 void play()
42 {
43 const juce::ScopedLock sl (lock);
44
45 setPosition (getPosition());
46 speed = 1.0;
47 }
48
49 // takes the play time directly from the engine's time - for recording, where it needs to be fixed
50 void playLockedToEngine (legacy::EditTimeRange rangeToPlay)
51 {
52 const juce::ScopedLock sl (lock);
53
54 play (rangeToPlay, false);
55 playoutSyncTime = 0;
56 streamSyncTime = 0;
57 }
58
59 void stop()
60 {
61 const juce::ScopedLock sl (lock);
62
63 auto t = getPosition();
64 speed = 0;
65 setPosition (t);
66 }
67
68 //==============================================================================
69 void setUserIsDragging (bool b)
70 {
71 userInteraction();
72 userDragging = b;
73 }
74
75 bool isUserDragging() const { return userDragging; }
76 juce::Time getLastUserInteractionTime() const { return userInteractionTime; }
77
78 //==============================================================================
80 {
82 : editRange1 (range1), isSplit (false) {}
83
85 : editRange1 (range1), editRange2 (range2), isSplit (true) {}
86
87 legacy::EditTimeRange editRange1, editRange2;
88 bool isSplit;
89 };
90
91 double streamTimeToSourceTime (double streamTime) const
92 {
93 const juce::ScopedLock sl (lock);
94
95 if (userDragging)
96 return playoutSyncTime + std::fmod (streamTime - streamSyncTime, getScrubbingBlockLengthSeconds());
97
98 if (looping && ! rollInToLoop)
99 return linearTimeToLoopTime (streamTimeToSourceTimeUnlooped (streamTime), playRange);
100
101 return streamTimeToSourceTimeUnlooped (streamTime);
102 }
103
104 double streamTimeToSourceTimeUnlooped (double streamTime) const
105 {
106 const juce::ScopedLock sl (lock);
107 return playoutSyncTime + (streamTime - streamSyncTime) * speed;
108 }
109
110 static double linearTimeToLoopTime (double time, legacy::EditTimeRange loop)
111 {
112 return linearTimeToLoopTime (time, loop.start, loop.getLength());
113 }
114
115 static double linearTimeToLoopTime (double time, double loopStart, double loopLen)
116 {
117 return loopStart + std::fmod (time - loopStart, loopLen);
118 }
119
120 EditTimeWindow streamTimeToEditWindow (legacy::EditTimeRange streamTime) const
121 {
122 constexpr double errorMargin = 0.000001;
123
124 auto s = streamTimeToSourceTimeUnlooped (streamTime.start);
125 auto e = streamTimeToSourceTimeUnlooped (streamTime.end);
126
127 if (userDragging)
128 {
129 auto loopStart = [this] { const juce::ScopedLock sl (lock); return playoutSyncTime; }();
130 auto loopLen = getScrubbingBlockLengthSeconds();
131 auto loopEnd = loopStart + loopLen;
132
133 s = linearTimeToLoopTime (s, loopStart, loopLen);
134 e = linearTimeToLoopTime (e, loopStart, loopLen);
135
136 if (s > e)
137 {
138 if (s >= loopEnd - errorMargin)
139 return EditTimeWindow ({ loopStart, e });
140
141 if (e <= loopStart + errorMargin)
142 return EditTimeWindow ({ s, loopEnd });
143
144 return EditTimeWindow ({ s, loopEnd }, { loopStart, e });
145 }
146 }
147
148 if (looping && ! rollInToLoop)
149 {
150 const auto pr = getLoopTimes();
151 s = linearTimeToLoopTime (s, pr);
152 e = linearTimeToLoopTime (e, pr);
153
154 if (s > e)
155 {
156 if (s >= pr.end - errorMargin) return EditTimeWindow ({ pr.start, e });
157 if (e <= pr.start + errorMargin) return EditTimeWindow ({ s, pr.end });
158
159 return EditTimeWindow ({ s, pr.end }, { pr.start, e });
160 }
161 }
162
163 return EditTimeWindow ({ s, e });
164 }
165
166 double getPosition() const
167 {
168 const juce::ScopedLock sl (lock);
169 return streamTimeToSourceTime (lastStreamTime);
170 }
171
172 double getUnloopedPosition() const
173 {
174 const juce::ScopedLock sl (lock);
175 return streamTimeToSourceTimeUnlooped (lastStreamTime);
176 }
177
178 // Adjust position without triggering a 'user interaction' change.
179 // Use when the position change actually maintains continuity - e.g. a tempo change.
180 void overridePosition (double newTime)
181 {
182 const juce::ScopedLock sl (lock);
183
184 if (looping && rollInToLoop)
185 newTime = juce::jmin (newTime, playRange.end);
186 else if (looping)
187 newTime = playRange.clipValue (newTime);
188
189 streamSyncTime = lastStreamTimeEnd;
190 playoutSyncTime = newTime;
191 }
192
193 //==============================================================================
194 bool isPlaying() const noexcept { return speed.load (std::memory_order_relaxed) != 0; }
195 bool isStopped() const noexcept { return speed.load (std::memory_order_relaxed) == 0; }
196 bool isLooping() const noexcept { return looping.load (std::memory_order_relaxed); }
197 bool isRollingIntoLoop() const noexcept { return rollInToLoop.load (std::memory_order_relaxed); }
198
199 legacy::EditTimeRange getLoopTimes() const noexcept { const juce::ScopedLock sl (lock); return playRange; }
200
201 void setLoopTimes (bool loop, legacy::EditTimeRange times)
202 {
203 if (looping != loop || (loop && times != getLoopTimes()))
204 {
205 const juce::ScopedLock sl (lock);
206
207 auto lastPos = getPosition();
208 looping = loop;
209 playRange = times;
210 setPosition (lastPos);
211 }
212 }
213
214 void setRollInToLoop (double t)
215 {
216 const juce::ScopedLock sl (lock);
217
218 rollInToLoop = true;
219 streamSyncTime = lastStreamTime;
220 playoutSyncTime = juce::jmin (t, playRange.end);
221 }
222
223 //==============================================================================
225 void deviceManagerPositionUpdate (double newTime, double newTimeEnd)
226 {
227 const juce::ScopedLock sl (lock);
228
229 lastStreamTime = newTime;
230 lastStreamTimeEnd = newTimeEnd;
231
232 if (rollInToLoop && getPosition() > playRange.start + 1.0)
233 rollInToLoop = false;
234 }
235
236private:
237 std::atomic<double> speed { 0 };
238 double streamSyncTime = 0, playoutSyncTime = 0;
239 legacy::EditTimeRange playRange;
240 double lastStreamTime = 0, lastStreamTimeEnd = 0;
241 juce::Time userInteractionTime;
243 std::atomic<bool> looping { false }, userDragging { false }, rollInToLoop { false };
244
246 static constexpr double getScrubbingBlockLengthSeconds() { return 0.08; }
247
248 void userInteraction() { userInteractionTime = juce::Time::getCurrentTime(); }
249
251};
252
253}} // namespace tracktion { inline namespace engine
static Time JUCE_CALLTYPE getCurrentTime() noexcept
void deviceManagerPositionUpdate(double newTime, double newTimeEnd)
called by the DeviceManager
T fmod(T... args)
T is_pointer_v
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
T load(T... args)
constexpr Type jmin(Type a, Type b)
times