Audacity 3.2.0
PlaybackSchedule.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file PlaybackSchedule.cpp
6
7 Paul Licameli split from AudioIOBase.cpp
8
9 **********************************************************************/
10
11#include "PlaybackSchedule.h"
12
13#include "AudioIOBase.h"
14#include "Envelope.h"
15#include "Mix.h"
16#include "Project.h"
17#include "SampleCount.h"
18
19#include <cmath>
20
22
24{
25 mRate = rate;
26}
27
29
31{
32 return Mixer::WarpOptions{ schedule.mEnvelope };
33}
34
37{
38 using namespace std::chrono;
39#if 1
40 // Shorter times than in the default policy so that responses, to changes of
41 // loop region or speed slider or other such controls, don't lag too much
42 return { 0.05s, 0.05s, 0.25s };
43#else
44/*
45The old values, going very far back.
46
47There are old comments in the code about larger batches of work filling the
48queue with samples, to reduce CPU usage. Maybe this doesn't matter with most
49modern machines, or maybe there will prove to be a need to choose the numbers
50more smartly than these hardcoded values. Maybe we will need to figure out
51adaptiveness of the buffer size by detecting how long the work takes. Maybe
52we can afford even smaller times.
53*/
54 return { 4.0s, 4.0s, 10.0s };
55#endif
56}
57
59{
60 return true;
61}
62
64 unsigned long outputFrames)
65{
66 // Called from portAudio thread, use GetSequenceTime()
67 auto diff = schedule.GetSequenceTime() - schedule.mT1;
68 if (schedule.ReversedTime())
69 diff *= -1;
70 return sampleCount(floor(diff * mRate + 0.5)) >= 0 &&
71 // Require also that output frames are all consumed from ring buffer
72 outputFrames == 0;
73}
74
76 PlaybackSchedule &schedule, double offset )
77{
78 auto time = schedule.GetSequenceTime() + offset;
79 time = std::clamp(time, schedule.mT0, schedule.mT1);
80 schedule.RealTimeInit( time );
81 return time;
82}
83
85{
86 using namespace std::chrono;
87 return 10ms;
88}
89
92{
93 // How many samples to produce for each channel.
94 const auto realTimeRemaining = schedule.RealTimeRemaining();
95 auto frames = available;
96 auto toProduce = frames;
97 double deltat = frames / mRate;
98
99 if (deltat > realTimeRemaining)
100 {
101 // Produce some extra silence so that the time queue consumer can
102 // satisfy its end condition
103 const double extraRealTime = (TimeQueueGrainSize + 1) / mRate;
104 auto extra = std::min( extraRealTime, deltat - realTimeRemaining );
105 auto realTime = realTimeRemaining + extra;
106 frames = realTime * mRate + 0.5;
107 toProduce = realTimeRemaining * mRate + 0.5;
108 schedule.RealTimeAdvance( realTime );
109 }
110 else
111 schedule.RealTimeAdvance( deltat );
112
113 return { available, frames, toProduce };
114}
115
116std::pair<double, double>
118 double trackTime, size_t nSamples )
119{
120 auto realDuration = nSamples / mRate;
121 if (schedule.ReversedTime())
122 realDuration *= -1.0;
123
124 if (schedule.mEnvelope)
125 trackTime =
126 schedule.SolveWarpedLength(trackTime, realDuration);
127 else
128 trackTime += realDuration;
129
130 if ( trackTime >= schedule.mT1 )
131 return { schedule.mT1, std::numeric_limits<double>::infinity() };
132 else
133 return { trackTime, trackTime };
134}
135
137 PlaybackSchedule &, const Mixers &, size_t, size_t)
138{
139 return true;
140}
141
143{
144 return false;
145}
146
147namespace {
150 ~OldDefaultPlaybackPolicy() override = default;
151};
152}
153
155{
156 if (mPolicyValid.load(std::memory_order_acquire) && mpPlaybackPolicy)
157 return *mpPlaybackPolicy;
158
159 static OldDefaultPlaybackPolicy defaultPolicy;
160 return defaultPolicy;
161}
162
164{
165 return const_cast<PlaybackSchedule&>(*this).GetPolicy();
166}
167
169 const double t0, const double t1,
170 const AudioIOStartStreamOptions &options,
171 const RecordingSchedule *pRecordingSchedule )
172{
173 mpPlaybackPolicy.reset();
174
175 if ( pRecordingSchedule )
176 // It does not make sense to apply the time warp during overdub recording,
177 // which defeats the purpose of making the recording synchronized with
178 // the existing audio. (Unless we figured out the inverse warp of the
179 // captured samples in real time.)
180 // So just quietly ignore the time track.
181 mEnvelope = nullptr;
182 else
183 mEnvelope = options.envelope;
184
185 mT0 = t0;
186 if (pRecordingSchedule)
187 mT0 -= pRecordingSchedule->mPreRoll;
188
189 mT1 = t1;
190 if (pRecordingSchedule)
191 // adjust mT1 so that we don't give paComplete too soon to fill up the
192 // desired length of recording
193 mT1 -= pRecordingSchedule->mLatencyCorrection;
194
195 // Main thread's initialization of mTime
197
198 if (options.policyFactory)
199 mpPlaybackPolicy = options.policyFactory(options);
200
201 mWarpedTime = 0.0;
203
204 mPolicyValid.store(true, std::memory_order_release);
205}
206
207double PlaybackSchedule::ComputeWarpedLength(double t0, double t1) const
208{
209 if (mEnvelope)
210 return mEnvelope->IntegralOfInverse(t0, t1);
211 else
212 return t1 - t0;
213}
214
215double PlaybackSchedule::SolveWarpedLength(double t0, double length) const
216{
217 if (mEnvelope)
218 return mEnvelope->SolveIntegralOfInverse(t0, length);
219 else
220 return t0 + length;
221}
222
223double PlaybackSchedule::RealDuration(double trackTime1) const
224{
225 return fabs(RealDurationSigned(trackTime1));
226}
227
228double PlaybackSchedule::RealDurationSigned(double trackTime1) const
229{
230 return ComputeWarpedLength(mT0, trackTime1);
231}
232
234{
235 return mWarpedLength - mWarpedTime;
236}
237
238void PlaybackSchedule::RealTimeAdvance( double increment )
239{
240 mWarpedTime += increment;
241}
242
243void PlaybackSchedule::RealTimeInit( double trackTime )
244{
245 mWarpedTime = RealDurationSigned( trackTime );
246}
247
249{
250 mWarpedTime = 0;
251}
252
254{
255 return mDuration - Consumed();
256}
257
259{
260 return std::max( 0.0, mPosition + TotalCorrection() );
261}
262
264{
265 return std::max(0.0, -( mPosition + TotalCorrection() ) );
266}
267
269
271{
272 mNodePool.clear();
273 mProducerNode = nullptr;
274 mConsumerNode = nullptr;
275}
276
278{
279 auto node = std::make_unique<Node>();
280 mProducerNode = mConsumerNode = node.get();
281 mProducerNode->active.test_and_set();
282 mProducerNode->records.resize(size);
283 mNodePool.clear();
284 mNodePool.emplace_back( std::move(node) );
285}
286
288 PlaybackSchedule &schedule, PlaybackSlice slice )
289{
290 auto &policy = schedule.GetPolicy();
291
292 auto node = mProducerNode;
293
294 if ( node == nullptr )
295 // Recording only. Don't fill the queue.
296 return;
297
298
299 auto written = node->written;
300 auto tail = node->tail.load(std::memory_order_acquire);
301 auto head = node->head.load(std::memory_order_relaxed);
302 auto time = mLastTime;
303
304 auto frames = slice.toProduce;
305
306 auto advanceTail = [&](double time)
307 {
308 auto newTail = (tail + 1) % static_cast<int>(node->records.size());
309 if((newTail > head && static_cast<size_t>(newTail - head) == node->records.size() - 1) ||
310 (newTail < head && static_cast<size_t>(head - newTail) == node->records.size() - 1))
311 {
312 try
313 {
314 Node* next = nullptr;
315 for(auto& p : mNodePool)
316 {
317 if(p.get() == node || p->active.test_and_set())
318 continue;
319
320 next = p.get();
321 //next->offset = 0; set on consumer thread
322 next->next.store(nullptr);
323 next->head.store(0);
324 next->tail.store(0);
325 break;
326 }
327 if(next == nullptr)
328 {
329 mNodePool.emplace_back(std::make_unique<Node>());
330 next = mNodePool.back().get();
331 }
332 //previous node had too low capacity to fit all slices,
333 //try enlarge capacity to avoid more reallocaitons
334 next->records.resize(node->records.size() * 2);
335 next->records[0].timeValue = time;
336
337 node->next.store(next);//make it visible to the consumer
338 mProducerNode = node = next;
339 head = 0;
340 newTail = 0;
341 }
342 catch(...)
343 {
344 //overwrite last grain...
345 newTail = tail;
346 }
347 }
348 else
349 node->records[newTail].timeValue = time;
350 tail = newTail;
351 node->written = 0;
352 };
353
354 //inv: space > 0
355 auto space = TimeQueueGrainSize - written;
356 while ( frames >= space )
357 {
358 const auto times = policy.AdvancedTrackTime( schedule, time, space);
359 time = times.second;
360 if (!std::isfinite(time))
361 time = times.first;
362 advanceTail(time);
363 written = 0;
364 frames -= space;
365 space = TimeQueueGrainSize;
366 }
367 // Last odd lot
368 if ( frames > 0 )
369 {
370 const auto times = policy.AdvancedTrackTime( schedule, time, frames );
371 time = times.second;
372 if (!std::isfinite(time))
373 time = times.first;
374 written += frames;
375 space -= frames;
376 }
377 // Produce constant times if there is also some silence in the slice
378 frames = slice.frames - slice.toProduce;
379 while (frames > 0 && frames >= space )
380 {
381 advanceTail(time);
382
383 frames -= space;
384 written = 0;
385 space = TimeQueueGrainSize;
386 }
387
388 mLastTime = time;
389 node->written = written + frames;
390 node->tail.store(tail, std::memory_order_release);
391}
392
394{
395 return mLastTime;
396}
397
399{
400 mLastTime = time;
401}
402
403double PlaybackSchedule::TimeQueue::Consumer( size_t nSamples, double rate )
404{
405 auto node = mConsumerNode;
406
407 if ( node == nullptr ) {
408 // Recording only. No scrub or playback time warp. Don't use the queue.
409 return ( mLastTime += nSamples / rate );
410 }
411
412 auto head = node->head.load(std::memory_order_acquire);
413 auto tail = node->tail.load(std::memory_order_relaxed);
414
415 auto offset = node->offset;
416 auto available = TimeQueueGrainSize - offset;
417
418 if(nSamples >= available)
419 {
420 do
421 {
422 offset = 0;
423 nSamples -= available;
424 if ( head == tail )
425 {
426 //Check if circular buffer was reallocated
427 if(const auto next = node->next.load())
428 {
429 node->offset = 0;
430 node->active.clear();
431
432 mConsumerNode = node = next;
433 head = 0;
434 tail = node->tail.load(std::memory_order_relaxed);
435 available = TimeQueueGrainSize;
436 }
437 else
438 {
439 //consumer is ahead of producer...
440 return node->records[head].timeValue;
441 }
442 }
443 else
444 {
445 head = (head + 1) % static_cast<int>(node->records.size());
446 available = TimeQueueGrainSize;
447 }
448 } while (nSamples >= available);
449 node->head.store(head, std::memory_order_release);
450 }
451 node->offset = offset + nSamples;
452 return node->records[head].timeValue;
453}
454
456{
457 //TODO: check that consumer and producer indeed suspended when called from AudioIoCallback
458 mLastTime = time;
459 if(mProducerNode != nullptr)
460 {
461 mConsumerNode = mProducerNode;
462 mConsumerNode->next.store(nullptr);
463 mConsumerNode->head.store(0);
464 mConsumerNode->tail.store(0);
465 mConsumerNode->written = 0;
466 mConsumerNode->offset = 0;
467 mConsumerNode->records[0].timeValue = time;
468 }
469}
int min(int a, int b)
constexpr size_t TimeQueueGrainSize
double SolveIntegralOfInverse(double t0, double area) const
Definition: Envelope.cpp:1350
double IntegralOfInverse(double t0, double t1) const
Definition: Envelope.cpp:1287
Directs which parts of tracks to fetch for playback.
virtual BufferTimes SuggestedBufferTimes(PlaybackSchedule &schedule)
Provide hints for construction of playback RingBuffer objects.
virtual std::pair< double, double > AdvancedTrackTime(PlaybackSchedule &schedule, double trackTime, size_t nSamples)
Compute a new point in a track's timeline from an old point and a real duration.
virtual double OffsetSequenceTime(PlaybackSchedule &schedule, double offset)
Called when the play head needs to jump a certain distance.
virtual bool AllowSeek(PlaybackSchedule &schedule)
Whether repositioning commands are allowed during playback.
std::vector< std::unique_ptr< Mixer > > Mixers
virtual void Finalize(PlaybackSchedule &schedule)
Called after stopping of an audio stream or an unsuccessful start.
virtual bool Looping(const PlaybackSchedule &schedule) const
virtual Mixer::WarpOptions MixerWarpOptions(PlaybackSchedule &schedule)
Options to use when constructing mixers for each playback track.
virtual void Initialize(PlaybackSchedule &schedule, double rate)
Called before starting an audio stream.
virtual bool RepositionPlayback(PlaybackSchedule &schedule, const Mixers &playbackMixers, size_t frames, size_t available)
AudioIO::FillPlayBuffers calls this to update its cursors into tracks for changes of position or spee...
virtual PlaybackSlice GetPlaybackSlice(PlaybackSchedule &schedule, size_t available)
Choose length of one fetch of samples from tracks in a call to AudioIO::FillPlayBuffers.
virtual bool Done(PlaybackSchedule &schedule, unsigned long outputFrames)
Returns true if schedule.GetSequenceTime() has reached the end of playback.
virtual ~PlaybackPolicy()=0
virtual std::chrono::milliseconds SleepInterval(PlaybackSchedule &schedule)
How long to wait between calls to AudioIO::SequenceBufferExchange.
std::vector< std::unique_ptr< Node > > mNodePool
void Producer(PlaybackSchedule &schedule, PlaybackSlice slice)
Enqueue track time value advanced by the slice according to schedule's PlaybackPolicy.
double GetLastTime() const
Return the last time saved by Producer.
double Consumer(size_t nSamples, double rate)
Find the track time value nSamples after the last consumed sample.
void Prime(double time)
Empty the queue and reassign the last produced time.
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
struct holding stream options, including a pointer to the time warp info and AudioIOListener and whet...
Definition: AudioIOBase.h:46
PolicyFactory policyFactory
Definition: AudioIOBase.h:75
const BoundedEnvelope * envelope
Definition: AudioIOBase.h:57
Immutable structure is an argument to Mixer's constructor.
Definition: MixerOptions.h:56
Times are in seconds.
std::atomic< Node * > next
Points to a node which should be used instead of current one when it becomes exhausted by a consumer ...
std::unique_ptr< PlaybackPolicy > mpPlaybackPolicy
double RealTimeRemaining() const
double mT0
Playback starts at offset of mT0, which is measured in seconds.
double mT1
Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 duri...
double SolveWarpedLength(double t0, double length) const
Compute how much unwarped time must have elapsed if length seconds of warped time has elapsed,...
bool ReversedTime() const
True if the end time is before the start time.
double RealDurationSigned(double trackTime1) const
const BoundedEnvelope * mEnvelope
void Init(double t0, double t1, const AudioIOStartStreamOptions &options, const RecordingSchedule *pRecordingSchedule)
std::atomic< bool > mPolicyValid
double RealDuration(double trackTime1) const
void RealTimeInit(double trackTime)
void RealTimeAdvance(double increment)
void SetSequenceTime(double time)
Set current track time value, unadjusted.
double GetSequenceTime() const
Get current track time value, unadjusted.
double ComputeWarpedLength(double t0, double t1) const
Compute signed duration (in seconds at playback) of the specified region of the track.
PlaybackPolicy & GetPolicy()
Describes an amount of contiguous (but maybe time-warped) data to be extracted from tracks to play.
const size_t toProduce
Not more than frames; the difference will be trailing silence.
const size_t frames
Total number of frames to be buffered.
double TotalCorrection() const
double ToDiscard() const
double ToConsume() const
double Consumed() const
The old default playback policy plays once and consumes no messages.