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
11#pragma once
12
13
14namespace tracktion { inline namespace graph
15{
16
17class PlayHead;
18
19//==============================================================================
20//==============================================================================
25{
27 : timelineRange1 (range1), isSplit (false) {}
28
30 : timelineRange1 (range1), timelineRange2 (range2), isSplit (true) {}
31
32 const juce::Range<int64_t> timelineRange1, timelineRange2;
33 const bool isSplit;
34};
35
36
40static inline SplitTimelineRange referenceSampleRangeToSplitTimelineRange (const PlayHead&, juce::Range<int64_t> referenceSampleRange);
41
42
43//==============================================================================
44//==============================================================================
52{
53public:
55 PlayHead() noexcept;
56
57 //==============================================================================
59 void setPosition (int64_t newPosition);
60
62 void play (juce::Range<int64_t> rangeToPlay, bool looped);
63
65 void play();
66
71 void playSyncedToRange (juce::Range<int64_t> rangeToPlay);
72
74 void stop();
75
77 int64_t getPosition() const;
78
81
85 void overridePosition (int64_t newPosition);
86
87 //==============================================================================
89 bool isPlaying() const noexcept;
90
92 bool isStopped() const noexcept;
93
95 bool isLooping() const noexcept;
96
98 bool isRollingIntoLoop() const noexcept;
99
101 juce::Range<int64_t> getLoopRange() const noexcept;
102
104 void setLoopRange (bool loop, juce::Range<int64_t> loopRange, bool updatePosition = true);
105
110 void setRollInToLoop (int64_t playbackPosition);
111
112 //==============================================================================
114 void setUserIsDragging (bool);
115
117 bool isUserDragging() const;
118
120 std::chrono::system_clock::time_point getLastUserInteractionTime() const;
121
122 //==============================================================================
124 void setScrubbingBlockLength (int64_t numSamples);
125
128
129 //==============================================================================
131 int64_t referenceSamplePositionToTimelinePosition (int64_t referenceSamplePosition) const;
132
135
138
141
142 //==============================================================================
145
148
153
154private:
155 //==============================================================================
156 struct SyncPositions
157 {
158 SyncPositions() noexcept = default;
159 int64_t referenceSyncPosition = 0;
160 int64_t playoutSyncPosition = 0;
161 };
162
164
165 //==============================================================================
166 MultipleWriterSeqLock<juce::Range<int64_t>> referenceSampleRange;
168 std::atomic<int64_t> scrubbingBlockLength { static_cast<int64_t> (0.08 * 44100.0) };
169
170 std::atomic<int> speed { 0 };
171 std::atomic<bool> looping { false }, userDragging { false }, rollInToLoop { false };
172
174
175 //==============================================================================
176 SyncPositions getSyncPositions() const { return syncPositions.load(); }
177 void setSyncPositions (SyncPositions newPositions) { syncPositions.store (newPositions); }
178
179 void userInteraction() { userInteractionTime = std::chrono::system_clock::now(); }
180};
181
182
183//==============================================================================
184//==============================================================================
185inline PlayHead::PlayHead() noexcept
186{
187 assert (scrubbingBlockLength.is_lock_free());
188}
189
190//==============================================================================
191inline void PlayHead::setPosition (int64_t newPosition)
192{
193 if (newPosition != getPosition())
194 userInteraction();
195
196 overridePosition (newPosition);
197}
198
199inline void PlayHead::play (juce::Range<int64_t> rangeToPlay, bool looped)
200{
201 timelinePlayRange.store (rangeToPlay);
202 looping = looped && (rangeToPlay.getLength() > 50);
203 setPosition (rangeToPlay.getStart());
204 speed = 1;
205}
206
207inline void PlayHead::play()
208{
210 speed = 1;
211}
212
214{
215 play (rangeToPlay, false);
216 setSyncPositions ({});
217}
218
219inline void PlayHead::stop()
220{
221 auto t = getPosition();
222 speed = 0;
223 setPosition (t);
224}
225
227{
228 return referenceSamplePositionToTimelinePosition (referenceSampleRange.load().getStart());
229}
230
232{
233 return referenceSamplePositionToTimelinePositionUnlooped (referenceSampleRange.load().getStart());
234}
235
236inline void PlayHead::overridePosition (int64_t newPosition)
237{
238 if (looping && rollInToLoop)
239 newPosition = std::min (newPosition, timelinePlayRange.load().getEnd());
240 else if (looping)
241 newPosition = timelinePlayRange.load().clipValue (newPosition);
242
243 SyncPositions newSyncPositions;
244 newSyncPositions.referenceSyncPosition = referenceSampleRange.load().getStart();
245 newSyncPositions.playoutSyncPosition = newPosition;
246 setSyncPositions (newSyncPositions);
247}
248
249//==============================================================================
250inline bool PlayHead::isPlaying() const noexcept { return speed.load (std::memory_order_relaxed) != 0; }
251
252inline bool PlayHead::isStopped() const noexcept { return speed.load (std::memory_order_relaxed) == 0; }
253
254inline bool PlayHead::isLooping() const noexcept { return looping.load (std::memory_order_relaxed); }
255
256inline bool PlayHead::isRollingIntoLoop() const noexcept { return rollInToLoop.load (std::memory_order_relaxed); }
257
258inline juce::Range<int64_t> PlayHead::getLoopRange() const noexcept { return timelinePlayRange.load(); }
259
260inline void PlayHead::setLoopRange (bool loop, juce::Range<int64_t> loopRange, bool updatePosition)
261{
262 if (looping != loop || (loop && loopRange != getLoopRange()))
263 {
264 auto lastPos = getPosition();
265 looping = loop;
266 timelinePlayRange.store (loopRange);
267
268 if (updatePosition)
269 setPosition (lastPos);
270 }
271}
272
273inline void PlayHead::setRollInToLoop (int64_t position)
274{
275 rollInToLoop = true;
276
277 SyncPositions newSyncPositions;
278 newSyncPositions.referenceSyncPosition = referenceSampleRange.load().getStart();
279 newSyncPositions.playoutSyncPosition = std::min (position, timelinePlayRange.load().getEnd());
280 setSyncPositions (newSyncPositions);
281}
282
283//==============================================================================
284inline void PlayHead::setUserIsDragging (bool b)
285{
286 userInteraction();
287 userDragging = b;
288}
289
290inline bool PlayHead::isUserDragging() const
291{
292 return userDragging;
293}
294
295inline std::chrono::system_clock::time_point PlayHead::getLastUserInteractionTime() const
296{
297 return userInteractionTime;
298}
299
300//==============================================================================
301inline void PlayHead::setScrubbingBlockLength (int64_t numSamples)
302{
303 scrubbingBlockLength = numSamples;
304}
305
307{
308 return scrubbingBlockLength.load (std::memory_order_relaxed);
309}
310
311//==============================================================================
312inline int64_t PlayHead::referenceSamplePositionToTimelinePosition (int64_t referenceSamplePosition) const
313{
314 const auto syncPos = getSyncPositions();
315
316 if (userDragging)
317 return syncPos.playoutSyncPosition + ((referenceSamplePosition - syncPos.referenceSyncPosition) % getScrubbingBlockLength());
318
319 if (looping && ! rollInToLoop)
320 return linearPositionToLoopPosition (referenceSamplePositionToTimelinePositionUnlooped (referenceSamplePosition), timelinePlayRange.load());
321
322 return referenceSamplePositionToTimelinePositionUnlooped (referenceSamplePosition);
323}
324
325inline int64_t PlayHead::referenceSamplePositionToTimelinePositionUnlooped (int64_t referenceSamplePosition) const
326{
327 const auto syncPos = getSyncPositions();
328 return syncPos.playoutSyncPosition + (referenceSamplePosition - syncPos.referenceSyncPosition) * speed;
329}
330
332{
333 const auto syncPos = getSyncPositions();
334 return { syncPos.playoutSyncPosition + (sourceReferenceSampleRange.getStart() - syncPos.referenceSyncPosition) * speed,
335 syncPos.playoutSyncPosition + (sourceReferenceSampleRange.getEnd() - syncPos.referenceSyncPosition) * speed };
336}
337
339{
340 const auto loopStart = loopRange.getStart();
341 return loopStart + juce::negativeAwareModulo ((position - loopStart), loopRange.getLength());
342}
343
344//==============================================================================
346{
347 referenceSampleRange.store (sampleRange);
348
349 if (rollInToLoop && getPosition() >= timelinePlayRange.load().getStart())
350 rollInToLoop = false;
351}
352
354{
355 return referenceSampleRange.load();
356}
357
359{
360 return getSyncPositions().playoutSyncPosition;
361}
362
363//==============================================================================
364//==============================================================================
365inline SplitTimelineRange referenceSampleRangeToSplitTimelineRange (const PlayHead& playHead, juce::Range<int64_t> referenceSampleRange)
366{
367 const auto unloopedRange = playHead.referenceSampleRangeToSourceRangeUnlooped (referenceSampleRange);
368 auto s = unloopedRange.getStart();
369 auto e = unloopedRange.getEnd();
370
371 if (playHead.isUserDragging())
372 {
373 auto loopStart = playHead.getPlayoutSyncPosition();
374 auto loopLen = playHead.getScrubbingBlockLength();
375 auto loopEnd = loopStart + loopLen;
376
377 s = PlayHead::linearPositionToLoopPosition (s, { loopStart, loopStart + loopLen });
378 e = PlayHead::linearPositionToLoopPosition (e, { loopStart, loopStart + loopLen });
379
380 if (s > e)
381 {
382 if (s >= loopEnd)
383 return { { loopStart, e } };
384
385 if (e <= loopStart)
386 return { { s, loopEnd } };
387
388 return { { s, loopEnd }, { loopStart, e } };
389 }
390 }
391
392 if (playHead.isLooping() && ! playHead.isRollingIntoLoop())
393 {
394 const auto pr = playHead.getLoopRange();
397
398 if (s > e)
399 {
400 if (s >= pr.getEnd()) return { { pr.getStart(), e } };
401 if (e <= pr.getStart()) return { { s, pr.getEnd() } };
402
403 return { { s, pr.getEnd() }, { pr.getStart(), e } };
404 }
405 }
406
407 return { { s, e } };
408}
409
410}}
assert
constexpr ValueType getStart() const noexcept
constexpr ValueType getEnd() const noexcept
constexpr ValueType getLength() const noexcept
Wraps a seqlock to allow a thread-safe object with wait-free reads with respect to each other.
Converts a monotonically increasing reference range in to a timeline range.
std::chrono::system_clock::time_point getLastUserInteractionTime() const
Returns the time of the last user interaction, either a setPosition or setUserIsDragging call.
int64_t getPosition() const
Returns the current timeline position.
void stop()
Stops the play head.
void overridePosition(int64_t newPosition)
Adjust position without triggering a 'user interaction' change.
bool isRollingIntoLoop() const noexcept
Returns true is the play head is looping but playing before the loop start position.
int64_t getScrubbingBlockLength() const
Returns the length of the small looped blocks to play while scrubbing.
void setLoopRange(bool loop, juce::Range< int64_t > loopRange, bool updatePosition=true)
Sets a playback range and whether to loop or not.
int64_t referenceSamplePositionToTimelinePositionUnlooped(int64_t referenceSamplePosition) const
Converts a reference sample position to a timeline position as if there was no looping set.
void setUserIsDragging(bool)
Sets the user dragging which logs a user interaction and enables scrubbing mode.
void setScrubbingBlockLength(int64_t numSamples)
Sets the length of the small looped blocks to play while scrubbing.
bool isUserDragging() const
Returns true if the user is dragging.
int64_t referenceSamplePositionToTimelinePosition(int64_t referenceSamplePosition) const
Converts a reference sample position to a timeline position.
juce::Range< int64_t > getReferenceSampleRange() const
Returns the reference sample count.
void setPosition(int64_t newPosition)
Sets the timeline position of the play head and if it is different logs a user interaction.
void play()
Starts playback from the last position.
bool isStopped() const noexcept
Returns true is the play head is currently stopped.
juce::Range< int64_t > getLoopRange() const noexcept
Returns the looped playback range.
void setReferenceSampleRange(juce::Range< int64_t > sampleRange)
Sets the reference sample count, adjusting the timeline if the play head is playing.
int64_t getPlayoutSyncPosition() const
Returns the playout sync position.
void setRollInToLoop(int64_t playbackPosition)
Puts the play head in to roll in to loop mode.
bool isPlaying() const noexcept
Returns true is the play head is currently playing.
bool isLooping() const noexcept
Returns true is the play head is in loop mode.
void playSyncedToRange(juce::Range< int64_t > rangeToPlay)
Takes the play position directly from the playout range.
juce::Range< int64_t > referenceSampleRangeToSourceRangeUnlooped(juce::Range< int64_t > sourceReferenceSampleRange) const
Converts a reference sample range to a timeline range as if there was no looping set.
static int64_t linearPositionToLoopPosition(int64_t position, juce::Range< int64_t > loopRange)
Converts a linear timeline position to a position wrapped in the loop.
PlayHead() noexcept
Creates a PlayHead.
int64_t getUnloopedPosition() const
Returns the current timeline position ignoring any loop range which might have been set.
T is_lock_free(T... args)
T is_pointer_v
T load(T... args)
T min(T... args)
IntegerType negativeAwareModulo(IntegerType dividend, const IntegerType divisor) noexcept
typedef int64_t
Represents a pair of timeline ranges which could be wraped around the loop end.