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 const auto time = schedule.GetSequenceTime() + offset;
79 schedule.RealTimeInit( time );
80 return time;
81}
82
84{
85 using namespace std::chrono;
86 return 10ms;
87}
88
91{
92 // How many samples to produce for each channel.
93 const auto realTimeRemaining = schedule.RealTimeRemaining();
94 auto frames = available;
95 auto toProduce = frames;
96 double deltat = frames / mRate;
97
98 if (deltat > realTimeRemaining)
99 {
100 // Produce some extra silence so that the time queue consumer can
101 // satisfy its end condition
102 const double extraRealTime = (TimeQueueGrainSize + 1) / mRate;
103 auto extra = std::min( extraRealTime, deltat - realTimeRemaining );
104 auto realTime = realTimeRemaining + extra;
105 frames = realTime * mRate + 0.5;
106 toProduce = realTimeRemaining * mRate + 0.5;
107 schedule.RealTimeAdvance( realTime );
108 }
109 else
110 schedule.RealTimeAdvance( deltat );
111
112 return { available, frames, toProduce };
113}
114
115std::pair<double, double>
117 double trackTime, size_t nSamples )
118{
119 auto realDuration = nSamples / mRate;
120 if (schedule.ReversedTime())
121 realDuration *= -1.0;
122
123 if (schedule.mEnvelope)
124 trackTime =
125 schedule.SolveWarpedLength(trackTime, realDuration);
126 else
127 trackTime += realDuration;
128
129 if ( trackTime >= schedule.mT1 )
130 return { schedule.mT1, std::numeric_limits<double>::infinity() };
131 else
132 return { trackTime, trackTime };
133}
134
136 PlaybackSchedule &, const Mixers &, size_t, size_t)
137{
138 return true;
139}
140
142{
143 return false;
144}
145
146namespace {
149 ~OldDefaultPlaybackPolicy() override = default;
150};
151}
152
154{
155 if (mPolicyValid.load(std::memory_order_acquire) && mpPlaybackPolicy)
156 return *mpPlaybackPolicy;
157
158 static OldDefaultPlaybackPolicy defaultPolicy;
159 return defaultPolicy;
160}
161
163{
164 return const_cast<PlaybackSchedule&>(*this).GetPolicy();
165}
166
168 const double t0, const double t1,
169 const AudioIOStartStreamOptions &options,
170 const RecordingSchedule *pRecordingSchedule )
171{
172 mpPlaybackPolicy.reset();
173
174 if ( pRecordingSchedule )
175 // It does not make sense to apply the time warp during overdub recording,
176 // which defeats the purpose of making the recording synchronized with
177 // the existing audio. (Unless we figured out the inverse warp of the
178 // captured samples in real time.)
179 // So just quietly ignore the time track.
180 mEnvelope = nullptr;
181 else
182 mEnvelope = options.envelope;
183
184 mT0 = t0;
185 if (pRecordingSchedule)
186 mT0 -= pRecordingSchedule->mPreRoll;
187
188 mT1 = t1;
189 if (pRecordingSchedule)
190 // adjust mT1 so that we don't give paComplete too soon to fill up the
191 // desired length of recording
192 mT1 -= pRecordingSchedule->mLatencyCorrection;
193
194 // Main thread's initialization of mTime
196
197 if (options.policyFactory)
198 mpPlaybackPolicy = options.policyFactory(options);
199
200 mWarpedTime = 0.0;
202
203 mPolicyValid.store(true, std::memory_order_release);
204}
205
206double PlaybackSchedule::ComputeWarpedLength(double t0, double t1) const
207{
208 if (mEnvelope)
209 return mEnvelope->IntegralOfInverse(t0, t1);
210 else
211 return t1 - t0;
212}
213
214double PlaybackSchedule::SolveWarpedLength(double t0, double length) const
215{
216 if (mEnvelope)
217 return mEnvelope->SolveIntegralOfInverse(t0, length);
218 else
219 return t0 + length;
220}
221
222double PlaybackSchedule::RealDuration(double trackTime1) const
223{
224 return fabs(RealDurationSigned(trackTime1));
225}
226
227double PlaybackSchedule::RealDurationSigned(double trackTime1) const
228{
229 return ComputeWarpedLength(mT0, trackTime1);
230}
231
233{
234 return mWarpedLength - mWarpedTime;
235}
236
237void PlaybackSchedule::RealTimeAdvance( double increment )
238{
239 mWarpedTime += increment;
240}
241
242void PlaybackSchedule::RealTimeInit( double trackTime )
243{
244 mWarpedTime = RealDurationSigned( trackTime );
245}
246
248{
249 mWarpedTime = 0;
250}
251
253{
254 return mDuration - Consumed();
255}
256
258{
259 return std::max( 0.0, mPosition + TotalCorrection() );
260}
261
263{
264 return std::max(0.0, -( mPosition + TotalCorrection() ) );
265}
266
268{
269 mData = Records{};
270 mHead = {};
271 mTail = {};
272}
273
275{
276 mData.resize(size);
277}
278
280 PlaybackSchedule &schedule, PlaybackSlice slice )
281{
282 auto &policy = schedule.GetPolicy();
283
284 if ( mData.empty() )
285 // Recording only. Don't fill the queue.
286 return;
287
288 // Don't check available space: assume it is enough because of coordination
289 // with RingBuffer.
290 auto index = mTail.mIndex;
291 auto time = mLastTime;
292 auto remainder = mTail.mRemainder;
293 auto space = TimeQueueGrainSize - remainder;
294 const auto size = mData.size();
295
296 // Produce advancing times
297 auto frames = slice.toProduce;
298 while ( frames >= space ) {
299 auto times = policy.AdvancedTrackTime( schedule, time, space );
300 time = times.second;
301 if (!std::isfinite(time))
302 time = times.first;
303 index = (index + 1) % size;
304 mData[ index ].timeValue = time;
305 frames -= space;
306 remainder = 0;
307 space = TimeQueueGrainSize;
308 }
309 // Last odd lot
310 if ( frames > 0 ) {
311 auto times = policy.AdvancedTrackTime( schedule, time, frames );
312 time = times.second;
313 if (!std::isfinite(time))
314 time = times.first;
315 remainder += frames;
316 space -= frames;
317 }
318
319 // Produce constant times if there is also some silence in the slice
320 frames = slice.frames - slice.toProduce;
321 while ( frames > 0 && frames >= space ) {
322 index = (index + 1) % size;
323 mData[ index ].timeValue = time;
324 frames -= space;
325 remainder = 0;
326 space = TimeQueueGrainSize;
327 }
328
329 mLastTime = time;
330 mTail.mRemainder = remainder + frames;
331 mTail.mIndex = index;
332}
333
335{
336 return mLastTime;
337}
338
340{
341 mLastTime = time;
342}
343
344double PlaybackSchedule::TimeQueue::Consumer( size_t nSamples, double rate )
345{
346 if ( mData.empty() ) {
347 // Recording only. No scrub or playback time warp. Don't use the queue.
348 return ( mLastTime += nSamples / rate );
349 }
350
351 // Don't check available space: assume it is enough because of coordination
352 // with RingBuffer.
353 auto remainder = mHead.mRemainder;
354 auto space = TimeQueueGrainSize - remainder;
355 const auto size = mData.size();
356 if ( nSamples >= space ) {
357 remainder = 0,
358 mHead.mIndex = (mHead.mIndex + 1) % size,
359 nSamples -= space;
360 if ( nSamples >= TimeQueueGrainSize )
361 mHead.mIndex =
362 (mHead.mIndex + ( nSamples / TimeQueueGrainSize ) ) % size,
363 nSamples %= TimeQueueGrainSize;
364 }
365 mHead.mRemainder = remainder + nSamples;
366 return mData[ mHead.mIndex ].timeValue;
367}
368
370{
371 mHead = mTail = {};
372 mLastTime = time;
373 if ( !mData.empty() )
374 mData[0].timeValue = time;
375}
int min(int a, int b)
constexpr size_t TimeQueueGrainSize
double SolveIntegralOfInverse(double t0, double area) const
Definition: Envelope.cpp:1306
double IntegralOfInverse(double t0, double t1) const
Definition: Envelope.cpp:1243
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.
NonInterfering< Cursor > mHead
Aligned to avoid false sharing.
void Producer(PlaybackSchedule &schedule, PlaybackSlice slice)
Enqueue track time value advanced by the slice according to schedule's PlaybackPolicy.
std::vector< Record > Records
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.
NonInterfering< Cursor > mTail
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:44
PolicyFactory policyFactory
Definition: AudioIOBase.h:73
const BoundedEnvelope * envelope
Definition: AudioIOBase.h:55
Immutable structure is an argument to Mixer's constructor.
Definition: MixerOptions.h:56
Times are in seconds.
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.