1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 PlaybackSchedule.h
6
7 Paul Licameli split from AudioIOBase.h
8
9 **********************************************************************/
10
11 #ifndef __AUDACITY_PLAYBACK_SCHEDULE__
12 #define __AUDACITY_PLAYBACK_SCHEDULE__
13
14 #include "MemoryX.h"
15 #include "Mix.h"
16 #include <atomic>
17 #include <chrono>
18 #include <vector>
19
20 #include <wx/event.h>
21
22 class AudacityProject;
23 struct AudioIOStartStreamOptions;
24 class BoundedEnvelope;
25 using PRCrossfadeData = std::vector< std::vector < float > >;
26 class PlayRegionEvent;
27
28 constexpr size_t TimeQueueGrainSize = 2000;
29
30 //! Communicate data atomically from one writer thread to one reader.
31 /*!
32 This is not a queue: it is not necessary for each write to be read.
33 Rather loss of a message is allowed: writer may overwrite.
34 Data must be default-constructible and either copyable or movable.
35 */
36 template<typename Data>
37 class MessageBuffer {
38 struct UpdateSlot {
39 std::atomic<bool> mBusy{ false };
40 Data mData;
41 };
42 NonInterfering<UpdateSlot> mSlots[2];
43
44 std::atomic<unsigned char> mLastWrittenSlot{ 0 };
45
46 public:
47 void Initialize();
48
49 //! Move data out (if available), or else copy it out
50 Data Read();
51
52 //! Copy data in
53 void Write( const Data &data );
54 //! Move data in
55 void Write( Data &&data );
56 };
57
58 template<typename Data>
Initialize()59 void MessageBuffer<Data>::Initialize()
60 {
61 for (auto &slot : mSlots)
62 // Lock both slots first, maybe spinning a little
63 while ( slot.mBusy.exchange( true, std::memory_order_acquire ) )
64 {}
65
66 mSlots[0].mData = {};
67 mSlots[1].mData = {};
68 mLastWrittenSlot.store( 0, std::memory_order_relaxed );
69
70 for (auto &slot : mSlots)
71 slot.mBusy.exchange( false, std::memory_order_release );
72 }
73
74 template<typename Data>
Read()75 Data MessageBuffer<Data>::Read()
76 {
77 // Whichever slot was last written, prefer to read that.
78 auto idx = mLastWrittenSlot.load( std::memory_order_relaxed );
79 idx = 1 - idx;
80 bool wasBusy = false;
81 do {
82 // This loop is unlikely to execute twice, but it might because the
83 // producer thread is writing a slot.
84 idx = 1 - idx;
85 wasBusy = mSlots[idx].mBusy.exchange( true, std::memory_order_acquire );
86 } while ( wasBusy );
87
88 // Copy the slot
89 auto result = std::move( mSlots[idx].mData );
90
91 mSlots[idx].mBusy.store( false, std::memory_order_release );
92
93 return result;
94 }
95
96 template<typename Data>
Write(const Data & data)97 void MessageBuffer<Data>::Write( const Data &data )
98 {
99 // Whichever slot was last written, prefer to write the other.
100 auto idx = mLastWrittenSlot.load( std::memory_order_relaxed );
101 bool wasBusy = false;
102 do {
103 // This loop is unlikely to execute twice, but it might because the
104 // consumer thread is reading a slot.
105 idx = 1 - idx;
106 wasBusy = mSlots[idx].mBusy.exchange( true, std::memory_order_acquire );
107 } while ( wasBusy );
108
109 mSlots[idx].mData = data;
110 mLastWrittenSlot.store( idx, std::memory_order_relaxed );
111
112 mSlots[idx].mBusy.store( false, std::memory_order_release );
113 }
114
115 template<typename Data>
Write(Data && data)116 void MessageBuffer<Data>::Write( Data &&data )
117 {
118 // Whichever slot was last written, prefer to write the other.
119 auto idx = mLastWrittenSlot.load( std::memory_order_relaxed );
120 bool wasBusy = false;
121 do {
122 // This loop is unlikely to execute twice, but it might because the
123 // consumer thread is reading a slot.
124 idx = 1 - idx;
125 wasBusy = mSlots[idx].mBusy.exchange( true, std::memory_order_acquire );
126 } while ( wasBusy );
127
128 mSlots[idx].mData = std::move( data );
129 mLastWrittenSlot.store( idx, std::memory_order_relaxed );
130
131 mSlots[idx].mBusy.store( false, std::memory_order_release );
132 }
133
134 struct RecordingSchedule {
135 double mPreRoll{};
136 double mLatencyCorrection{}; // negative value usually
137 double mDuration{};
138 PRCrossfadeData mCrossfadeData;
139
140 // These are initialized by the main thread, then updated
141 // only by the thread calling TrackBufferExchange:
142 double mPosition{};
143 bool mLatencyCorrected{};
144
TotalCorrectionRecordingSchedule145 double TotalCorrection() const { return mLatencyCorrection - mPreRoll; }
146 double ToConsume() const;
147 double Consumed() const;
148 double ToDiscard() const;
149 };
150
151 class Mixer;
152 struct PlaybackSchedule;
153
154 //! Describes an amount of contiguous (but maybe time-warped) data to be extracted from tracks to play
155 struct PlaybackSlice {
156 const size_t frames; //!< Total number of frames to be buffered
157 const size_t toProduce; //!< Not more than `frames`; the difference will be trailing silence
158
159 //! Constructor enforces some invariants
160 /*! @invariant `result.toProduce <= result.frames && result.frames <= available`
161 */
PlaybackSlicePlaybackSlice162 PlaybackSlice(
163 size_t available, size_t frames_, size_t toProduce_)
164 : frames{ std::min(available, frames_) }
165 , toProduce{ std::min(toProduce_, frames) }
166 {}
167 };
168
169 //! Directs which parts of tracks to fetch for playback
170 /*!
171 A non-default policy object may be created each time playback begins, and if so it is destroyed when
172 playback stops, not reused in the next playback.
173
174 Methods of the object are passed a PlaybackSchedule as context.
175 */
176 class PlaybackPolicy {
177 public:
178 //! @section Called by the main thread
179
180 virtual ~PlaybackPolicy() = 0;
181
182 //! Called before starting an audio stream
183 virtual void Initialize( PlaybackSchedule &schedule, double rate );
184
185 //! Called after stopping of an audio stream or an unsuccessful start
186 virtual void Finalize( PlaybackSchedule &schedule );
187
188 //! Options to use when constructing mixers for each playback track
189 virtual Mixer::WarpOptions MixerWarpOptions(PlaybackSchedule &schedule);
190
191 //! Times are in seconds
192 struct BufferTimes {
193 double batchSize; //!< Try to put at least this much into the ring buffer in each pass
194 double latency; //!< Try not to let ring buffer contents fall below this
195 double ringBufferDelay; //!< Length of ring buffer in seconds
196 };
197 //! Provide hints for construction of playback RingBuffer objects
198 virtual BufferTimes SuggestedBufferTimes(PlaybackSchedule &schedule);
199
200 //! @section Called by the PortAudio callback thread
201
202 //! Whether repositioning commands are allowed during playback
203 virtual bool AllowSeek( PlaybackSchedule &schedule );
204
205 //! Returns true if schedule.GetTrackTime() has reached the end of playback
206 virtual bool Done( PlaybackSchedule &schedule,
207 unsigned long outputFrames //!< how many playback frames were taken from RingBuffers
208 );
209
210 //! Called when the play head needs to jump a certain distance
211 /*! @param offset signed amount requested to be added to schedule::GetTrackTime()
212 @return the new value that will be set as the schedule's track time
213 */
214 virtual double OffsetTrackTime( PlaybackSchedule &schedule, double offset );
215
216 //! @section Called by the AudioIO::TrackBufferExchange thread
217
218 //! How long to wait between calls to AudioIO::TrackBufferExchange
219 virtual std::chrono::milliseconds
220 SleepInterval( PlaybackSchedule &schedule );
221
222 //! Choose length of one fetch of samples from tracks in a call to AudioIO::FillPlayBuffers
223 virtual PlaybackSlice GetPlaybackSlice( PlaybackSchedule &schedule,
224 size_t available //!< upper bound for the length of the fetch
225 );
226
227 //! Compute a new point in a track's timeline from an old point and a real duration
228 /*!
229 Needed because playback might be at non-unit speed.
230
231 Called one or more times between GetPlaybackSlice and RepositionPlayback,
232 until the sum of the nSamples values equals the most recent playback slice
233 (including any trailing silence).
234
235 @return a pair, which indicates a discontinuous jump when its members are not equal, or
236 specially the end of playback when the second member is infinite
237 */
238 virtual std::pair<double, double>
239 AdvancedTrackTime( PlaybackSchedule &schedule,
240 double trackTime, size_t nSamples );
241
242 using Mixers = std::vector<std::unique_ptr<Mixer>>;
243
244 //! AudioIO::FillPlayBuffers calls this to update its cursors into tracks for changes of position or speed
245 /*!
246 @return if true, AudioIO::FillPlayBuffers stops producing samples even if space remains
247 */
248 virtual bool RepositionPlayback(
249 PlaybackSchedule &schedule, const Mixers &playbackMixers,
250 size_t frames, //!< how many samples were just now buffered for play
251 size_t available //!< how many more samples may be buffered
252 );
253
254 //! @section To be removed
255
256 virtual bool Looping( const PlaybackSchedule &schedule ) const;
257
258 protected:
259 double mRate = 0;
260 };
261
262 struct AUDACITY_DLL_API PlaybackSchedule {
263
264 /// Playback starts at offset of mT0, which is measured in seconds.
265 double mT0;
266 /// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing.
267 double mT1;
268 /// Current track time position during playback, in seconds.
269 /// Initialized by the main thread but updated by worker threads during
270 /// playback or recording, and periodically reread by the main thread for
271 /// purposes such as display update.
272 std::atomic<double> mTime;
273
274 /// Accumulated real time (not track position), starting at zero (unlike
275 /// mTime), and wrapping back to zero each time around looping play.
276 /// Thus, it is the length in real seconds between mT0 and mTime.
277 double mWarpedTime;
278
279 /// Real length to be played (if looping, for each pass) after warping via a
280 /// time track, computed just once when starting the stream.
281 /// Length in real seconds between mT0 and mT1. Always positive.
282 double mWarpedLength;
283
284 // mWarpedTime and mWarpedLength are irrelevant when scrubbing,
285 // else they are used in updating mTime,
286 // and when not scrubbing or playing looped, mTime is also used
287 // in the test for termination of playback.
288
289 // with ComputeWarpedLength, it is now possible the calculate the warped length with 100% accuracy
290 // (ignoring accumulated rounding errors during playback) which fixes the 'missing sound at the end' bug
291
292 const BoundedEnvelope *mEnvelope;
293
294 //! A circular buffer
295 /*
296 Holds track time values corresponding to every nth sample in the
297 playback buffers, for the large n == TimeQueueGrainSize.
298
299 The "producer" is the Audio thread that fetches samples from tracks and
300 fills the playback RingBuffers. The "consumer" is the high-latency
301 PortAudio thread that drains the RingBuffers. The atomics in the
302 RingBuffer implement lock-free synchronization.
303
304 This other structure relies on the RingBuffer's synchronization, and adds
305 other information to the stream of samples: which track times they
306 correspond to.
307
308 The consumer thread uses that information, and also makes known to the main
309 thread, what the last consumed track time is. The main thread can use that
310 for other purposes such as refreshing the display of the play head position.
311 */
312 class TimeQueue {
313 public:
314
315 //! @section called by main thread
316
317 void Clear();
318 void Resize(size_t size);
319
320 //! @section Called by the AudioIO::TrackBufferExchange thread
321
322 //! Enqueue track time value advanced by `nSamples` according to `schedule`'s PlaybackPolicy
323 void Producer( PlaybackSchedule &schedule, size_t nSamples );
324
325 //! Return the last time saved by Producer
326 double GetLastTime() const;
327
328 void SetLastTime(double time);
329
330 //! @section called by PortAudio callback thread
331
332 //! Find the track time value `nSamples` after the last consumed sample
333 double Consumer( size_t nSamples, double rate );
334
335 //! @section called by any thread while producer and consumer are suspended
336
337 //! Empty the queue and reassign the last produced time
338 /*! Assumes producer and consumer are suspended */
339 void Prime( double time );
340
341 private:
342 struct Record {
343 double timeValue;
344 // More fields to come
345 };
346 using Records = std::vector<Record>;
347 Records mData;
348 double mLastTime {};
349 struct Cursor {
350 size_t mIndex {};
351 size_t mRemainder {};
352 };
353 //! Aligned to avoid false sharing
354 NonInterfering<Cursor> mHead, mTail;
355 } mTimeQueue;
356
357 PlaybackPolicy &GetPolicy();
358 const PlaybackPolicy &GetPolicy() const;
359
360 void Init(
361 double t0, double t1,
362 const AudioIOStartStreamOptions &options,
363 const RecordingSchedule *pRecordingSchedule );
364
365 /** @brief Compute signed duration (in seconds at playback) of the specified region of the track.
366 *
367 * Takes a region of the time track (specified by the unwarped time points in the project), and
368 * calculates how long it will actually take to play this region back, taking the time track's
369 * warping effects into account.
370 * @param t0 unwarped time to start calculation from
371 * @param t1 unwarped time to stop calculation at
372 * @return the warped duration in seconds, negated if `t0 > t1`
373 */
374 double ComputeWarpedLength(double t0, double t1) const;
375
376 /** @brief Compute how much unwarped time must have elapsed if length seconds of warped time has
377 * elapsed, and add to t0
378 *
379 * @param t0 The unwarped time (seconds from project start) at which to start
380 * @param length How many seconds of real time went past; signed
381 * @return The end point (in seconds from project start) as unwarped time
382 */
383 double SolveWarpedLength(double t0, double length) const;
384
385 /** \brief True if the end time is before the start time */
ReversedTimePlaybackSchedule386 bool ReversedTime() const
387 {
388 return mT1 < mT0;
389 }
390
391 /** \brief Get current track time value, unadjusted
392 *
393 * Returns a time in seconds.
394 */
GetTrackTimePlaybackSchedule395 double GetTrackTime() const
396 { return mTime.load(std::memory_order_relaxed); }
397
398 /** \brief Set current track time value, unadjusted
399 */
SetTrackTimePlaybackSchedule400 void SetTrackTime( double time )
401 { mTime.store(time, std::memory_order_relaxed); }
402
ResetModePlaybackSchedule403 void ResetMode() {
404 mPolicyValid.store(false, std::memory_order_release);
405 }
406
407 // Convert time between mT0 and argument to real duration, according to
408 // time track if one is given; result is always nonnegative
409 double RealDuration(double trackTime1) const;
410
411 // Convert time between mT0 and argument to real duration, according to
412 // time track if one is given; may be negative
413 double RealDurationSigned(double trackTime1) const;
414
415 // How much real time left?
416 double RealTimeRemaining() const;
417
418 // Advance the real time position
419 void RealTimeAdvance( double increment );
420
421 // Determine starting duration within the first pass -- sometimes not
422 // zero
423 void RealTimeInit( double trackTime );
424
425 void RealTimeRestart();
426
427 private:
428 std::unique_ptr<PlaybackPolicy> mpPlaybackPolicy;
429 std::atomic<bool> mPolicyValid{ false };
430 };
431
432 class NewDefaultPlaybackPolicy final
433 : public PlaybackPolicy
434 , public NonInterferingBase
435 , public wxEvtHandler
436 {
437 public:
438 NewDefaultPlaybackPolicy( AudacityProject &project,
439 double trackEndTime, double loopEndTime,
440 bool loopEnabled, bool variableSpeed);
441 ~NewDefaultPlaybackPolicy() override;
442
443 void Initialize( PlaybackSchedule &schedule, double rate ) override;
444
445 Mixer::WarpOptions MixerWarpOptions(PlaybackSchedule &schedule) override;
446
447 BufferTimes SuggestedBufferTimes(PlaybackSchedule &schedule) override;
448
449 bool Done( PlaybackSchedule &schedule, unsigned long ) override;
450
451 PlaybackSlice GetPlaybackSlice(
452 PlaybackSchedule &schedule, size_t available ) override;
453
454 std::pair<double, double>
455 AdvancedTrackTime( PlaybackSchedule &schedule,
456 double trackTime, size_t nSamples ) override;
457
458 bool RepositionPlayback(
459 PlaybackSchedule &schedule, const Mixers &playbackMixers,
460 size_t frames, size_t available ) override;
461
462 bool Looping( const PlaybackSchedule & ) const override;
463
464 private:
465 bool RevertToOldDefault( const PlaybackSchedule &schedule ) const;
466 void OnPlayRegionChange(PlayRegionEvent &evt);
467 void OnPlaySpeedChange(wxCommandEvent &evt);
468 void WriteMessage();
469 double GetPlaySpeed();
470
471 AudacityProject &mProject;
472
473 // The main thread writes changes in response to user events, and
474 // the audio thread later reads, and changes the playback.
475 struct SlotData {
476 double mPlaySpeed;
477 double mT0;
478 double mT1;
479 bool mLoopEnabled;
480 };
481 MessageBuffer<SlotData> mMessageChannel;
482
483 double mLastPlaySpeed{ 1.0 };
484 const double mTrackEndTime;
485 double mLoopEndTime;
486 size_t mRemaining{ 0 };
487 bool mProgress{ true };
488 bool mLoopEnabled{ true };
489 bool mVariableSpeed{ false };
490 };
491 #endif
492