Audacity 3.2.0
PlaybackSchedule.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 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 "ProjectAudioIO.h"
18#include "SampleCount.h"
19#include "ViewInfo.h" // for PlayRegionEvent
20
21#include <cmath>
22
24
26{
27 mRate = rate;
28}
29
31
33{
34 return Mixer::WarpOptions{ schedule.mEnvelope };
35}
36
39{
40 using namespace std::chrono;
41#if 1
42 // Shorter times than in the default policy so that responses, to changes of
43 // loop region or speed slider or other such controls, don't lag too much
44 return { 0.05s, 0.05s, 0.25s };
45#else
46/*
47The old values, going very far back.
48
49There are old comments in the code about larger batches of work filling the
50queue with samples, to reduce CPU usage. Maybe this doesn't matter with most
51modern machines, or maybe there will prove to be a need to choose the numbers
52more smartly than these hardcoded values. Maybe we will need to figure out
53adaptiveness of the buffer size by detecting how long the work takes. Maybe
54we can afford even smaller times.
55*/
56 return { 4.0s, 4.0s, 10.0s };
57#endif
58}
59
61{
62 return true;
63}
64
66 unsigned long outputFrames)
67{
68 // Called from portAudio thread, use GetTrackTime()
69 auto diff = schedule.GetTrackTime() - schedule.mT1;
70 if (schedule.ReversedTime())
71 diff *= -1;
72 return sampleCount(floor(diff * mRate + 0.5)) >= 0 &&
73 // Require also that output frames are all consumed from ring buffer
74 outputFrames == 0;
75}
76
78 PlaybackSchedule &schedule, double offset )
79{
80 const auto time = schedule.GetTrackTime() + offset;
81 schedule.RealTimeInit( time );
82 return time;
83}
84
86{
87 using namespace std::chrono;
88 return 10ms;
89}
90
93{
94 // How many samples to produce for each channel.
95 const auto realTimeRemaining = schedule.RealTimeRemaining();
96 auto frames = available;
97 auto toProduce = frames;
98 double deltat = frames / mRate;
99
100 if (deltat > realTimeRemaining)
101 {
102 // Produce some extra silence so that the time queue consumer can
103 // satisfy its end condition
104 const double extraRealTime = (TimeQueueGrainSize + 1) / mRate;
105 auto extra = std::min( extraRealTime, deltat - realTimeRemaining );
106 auto realTime = realTimeRemaining + extra;
107 frames = realTime * mRate;
108 toProduce = realTimeRemaining * mRate;
109 schedule.RealTimeAdvance( realTime );
110 }
111 else
112 schedule.RealTimeAdvance( deltat );
113
114 return { available, frames, toProduce };
115}
116
117std::pair<double, double>
119 double trackTime, size_t nSamples )
120{
121 auto realDuration = nSamples / mRate;
122 if (schedule.ReversedTime())
123 realDuration *= -1.0;
124
125 if (schedule.mEnvelope)
126 trackTime =
127 schedule.SolveWarpedLength(trackTime, realDuration);
128 else
129 trackTime += realDuration;
130
131 if ( trackTime >= schedule.mT1 )
132 return { schedule.mT1, std::numeric_limits<double>::infinity() };
133 else
134 return { trackTime, trackTime };
135}
136
138 PlaybackSchedule &, const Mixers &, size_t, size_t)
139{
140 return true;
141}
142
144{
145 return false;
146}
147
148namespace {
151 ~OldDefaultPlaybackPolicy() override = default;
152};
153}
154
156{
157 if (mPolicyValid.load(std::memory_order_acquire) && mpPlaybackPolicy)
158 return *mpPlaybackPolicy;
159
160 static OldDefaultPlaybackPolicy defaultPolicy;
161 return defaultPolicy;
162}
163
165{
166 return const_cast<PlaybackSchedule&>(*this).GetPolicy();
167}
168
170 double trackEndTime, double loopEndTime,
171 bool loopEnabled, bool variableSpeed )
172 : mProject{ project }
173 , mTrackEndTime{ trackEndTime }
174 , mLoopEndTime{ loopEndTime }
175 , mLoopEnabled{ loopEnabled }
176 , mVariableSpeed{ variableSpeed }
177{}
178
180
182 PlaybackSchedule &schedule, double rate )
183{
184 PlaybackPolicy::Initialize(schedule, rate);
187 schedule.mT0, mLoopEndTime, mLoopEnabled } );
188
191 if (mVariableSpeed)
192 mProject.Bind( EVT_PLAY_SPEED_CHANGE,
194}
195
197 PlaybackSchedule &schedule)
198{
199 if (mVariableSpeed)
200 // Enable variable rate mixing
201 return Mixer::WarpOptions(0.01, 32.0, GetPlaySpeed());
202 else
203 return PlaybackPolicy::MixerWarpOptions(schedule);
204}
205
208{
209 // Shorter times than in the default policy so that responses to changes of
210 // loop region or speed slider don't lag too much
211 using namespace std::chrono;
212 return { 0.05s, 0.05s, 0.25s };
213}
214
216{
217 return !mLoopEnabled ||
218 // Even if loop is enabled, ignore it if right of looping region
220}
221
223 PlaybackSchedule &schedule, unsigned long outputFrames )
224{
225 if (RevertToOldDefault(schedule)) {
226 auto diff = schedule.GetTrackTime() - schedule.mT1;
227 if (schedule.ReversedTime())
228 diff *= -1;
229 return sampleCount(floor(diff * mRate + 0.5)) >= 0;
230 }
231 return false;
232}
233
236 PlaybackSchedule &schedule, size_t available)
237{
238 // How many samples to produce for each channel.
239 const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
240 mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
241
242 auto frames = available;
243 auto toProduce = frames;
244 double deltat = (frames / mRate) * mLastPlaySpeed;
245
246 if (deltat > realTimeRemaining) {
247 toProduce = frames = 0.5 + (realTimeRemaining * mRate) / mLastPlaySpeed;
248 auto realTime = realTimeRemaining;
249 double extra = 0;
250 if (RevertToOldDefault(schedule)) {
251 // Produce some extra silence so that the time queue consumer can
252 // satisfy its end condition
253 const double extraRealTime =
255 extra = std::min( extraRealTime, deltat - realTimeRemaining );
256 frames = ((realTimeRemaining + extra) * mRate) / mLastPlaySpeed;
257 }
258 schedule.RealTimeAdvance( realTimeRemaining + extra );
259 }
260 else
261 schedule.RealTimeAdvance( deltat );
262
263 // Don't fall into an infinite loop, if loop-playing a selection
264 // that is so short, it has no samples: detect that case
265 if (frames == 0) {
266 bool progress = (schedule.mWarpedTime != 0.0);
267 if (!progress)
268 // Cause FillPlayBuffers to make progress, filling all available with 0
269 frames = available, toProduce = 0;
270 }
271 return { available, frames, toProduce };
272}
273
275 PlaybackSchedule &schedule, double trackTime, size_t nSamples )
276{
277 bool revert = RevertToOldDefault(schedule);
278 if (!mVariableSpeed && revert)
279 return PlaybackPolicy::AdvancedTrackTime(schedule, trackTime, nSamples);
280
281 mRemaining -= std::min(mRemaining, nSamples);
282 if ( mRemaining == 0 && !revert )
283 // Wrap to start
284 return { schedule.mT1, schedule.mT0 };
285
286 // Defense against cases that might cause loops not to terminate
287 if ( fabs(schedule.mT0 - schedule.mT1) < 1e-9 )
288 return {schedule.mT0, schedule.mT0};
289
290 auto realDuration = (nSamples / mRate) * mLastPlaySpeed;
291 if (schedule.ReversedTime())
292 realDuration *= -1.0;
293
294 if (schedule.mEnvelope)
295 trackTime =
296 schedule.SolveWarpedLength(trackTime, realDuration);
297 else
298 trackTime += realDuration;
299
300 return { trackTime, trackTime };
301}
302
304 PlaybackSchedule &schedule, const Mixers &playbackMixers,
305 size_t frames, size_t available )
306{
307 // This executes in the TrackBufferExchange thread
308 auto data = mMessageChannel.Read();
309
310 bool speedChange = false;
311 if (mVariableSpeed) {
312 speedChange = (mLastPlaySpeed != data.mPlaySpeed);
313 mLastPlaySpeed = data.mPlaySpeed;
314 }
315
316 bool empty = (data.mT0 >= data.mT1);
317 bool kicked = false;
318
319 // Amount in seconds by which right boundary can be moved left of the play
320 // head, yet loop play in progress will still capture the head
321 constexpr auto allowance = 0.5;
322
323 // Looping may become enabled if the main thread said so, but require too
324 // that the loop region is non-empty and the play head is not far to its
325 // right
326 bool loopWasEnabled = !RevertToOldDefault(schedule);
327 mLoopEnabled = data.mLoopEnabled && !empty &&
328 schedule.mTimeQueue.GetLastTime() <= data.mT1 + allowance;
329
330 // Four cases: looping transitions off, or transitions on, or stays on,
331 // or stays off.
332 // Besides which, the variable speed slider may have changed.
333
334 // If looping transitions on, or remains on and the region changed,
335 // adjust the schedule...
336 auto mine = std::tie(schedule.mT0, mLoopEndTime);
337 auto theirs = std::tie(data.mT0, data.mT1);
338 if ( mLoopEnabled ? (mine != theirs) : loopWasEnabled ) {
339 kicked = true;
340 if (!empty) {
341 mine = theirs;
342 schedule.mT1 = data.mT1;
343 }
344 if (!mLoopEnabled)
345 // Continue play to the end
346 schedule.mT1 = std::max(schedule.mT0, mTrackEndTime);
347 schedule.mWarpedLength = schedule.RealDuration(schedule.mT1);
348
349 auto newTime = schedule.mTimeQueue.GetLastTime();
350#if 0
351 // This would make play jump forward or backward into the adjusted
352 // looping region if not already in it
353 newTime = std::clamp(newTime, schedule.mT0, schedule.mT1);
354#endif
355
356 if (newTime >= schedule.mT1 && mLoopEnabled)
357 newTime = schedule.mT0;
358
359 // So that the play head will redraw in the right place:
360 schedule.mTimeQueue.SetLastTime(newTime);
361
362 schedule.RealTimeInit(newTime);
363 const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
364 mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
365 }
366 else if (speedChange)
367 // Don't return early
368 kicked = true;
369 else {
370 // ... else the region did not change, or looping is now off, in
371 // which case we have nothing special to do
372 if (RevertToOldDefault(schedule))
373 return PlaybackPolicy::RepositionPlayback( schedule, playbackMixers,
374 frames, available);
375 }
376
377 // msmeyer: If playing looped, check if we are at the end of the buffer
378 // and if yes, restart from the beginning.
379 if (mRemaining <= 0)
380 {
381 // Looping jumps left
382 for (auto &pMixer : playbackMixers)
383 pMixer->SetTimesAndSpeed(
384 schedule.mT0, schedule.mT1, mLastPlaySpeed, true );
385 schedule.RealTimeRestart();
386 }
387 else if (kicked)
388 {
389 // Play bounds need redefinition
390 const auto time = schedule.mTimeQueue.GetLastTime();
391 for (auto &pMixer : playbackMixers) {
392 // So that the mixer will fetch the next samples from the right place:
393 pMixer->SetTimesAndSpeed( time, schedule.mT1, mLastPlaySpeed );
394 pMixer->Reposition(time, true);
395 }
396 }
397 return false;
398}
399
401{
402 return mLoopEnabled;
403}
404
406{
407 // This executes in the main thread
408 WriteMessage();
409}
410
412{
413 evt.Skip(); // Let other listeners hear the event too
414 WriteMessage();
415}
416
418{
419 const auto &region = ViewInfo::Get( mProject ).playRegion;
420 mMessageChannel.Write( { GetPlaySpeed(),
421 region.GetStart(), region.GetEnd(), region.Active()
422 } );
423}
424
426{
427 return mVariableSpeed
429 : 1.0;
430}
431
433 const double t0, const double t1,
434 const AudioIOStartStreamOptions &options,
435 const RecordingSchedule *pRecordingSchedule )
436{
437 mpPlaybackPolicy.reset();
438
439 if ( pRecordingSchedule )
440 // It does not make sense to apply the time warp during overdub recording,
441 // which defeats the purpose of making the recording synchronized with
442 // the existing audio. (Unless we figured out the inverse warp of the
443 // captured samples in real time.)
444 // So just quietly ignore the time track.
445 mEnvelope = nullptr;
446 else
447 mEnvelope = options.envelope;
448
449 mT0 = t0;
450 if (pRecordingSchedule)
451 mT0 -= pRecordingSchedule->mPreRoll;
452
453 mT1 = t1;
454 if (pRecordingSchedule)
455 // adjust mT1 so that we don't give paComplete too soon to fill up the
456 // desired length of recording
457 mT1 -= pRecordingSchedule->mLatencyCorrection;
458
459 // Main thread's initialization of mTime
460 SetTrackTime( mT0 );
461
462 if (options.policyFactory)
463 mpPlaybackPolicy = options.policyFactory(options);
464
465 mWarpedTime = 0.0;
467
468 mPolicyValid.store(true, std::memory_order_release);
469}
470
471double PlaybackSchedule::ComputeWarpedLength(double t0, double t1) const
472{
473 if (mEnvelope)
474 return mEnvelope->IntegralOfInverse(t0, t1);
475 else
476 return t1 - t0;
477}
478
479double PlaybackSchedule::SolveWarpedLength(double t0, double length) const
480{
481 if (mEnvelope)
482 return mEnvelope->SolveIntegralOfInverse(t0, length);
483 else
484 return t0 + length;
485}
486
487double PlaybackSchedule::RealDuration(double trackTime1) const
488{
489 return fabs(RealDurationSigned(trackTime1));
490}
491
492double PlaybackSchedule::RealDurationSigned(double trackTime1) const
493{
494 return ComputeWarpedLength(mT0, trackTime1);
495}
496
498{
499 return mWarpedLength - mWarpedTime;
500}
501
502void PlaybackSchedule::RealTimeAdvance( double increment )
503{
504 mWarpedTime += increment;
505}
506
507void PlaybackSchedule::RealTimeInit( double trackTime )
508{
509 mWarpedTime = RealDurationSigned( trackTime );
510}
511
513{
514 mWarpedTime = 0;
515}
516
518{
519 return mDuration - Consumed();
520}
521
523{
524 return std::max( 0.0, mPosition + TotalCorrection() );
525}
526
528{
529 return std::max(0.0, -( mPosition + TotalCorrection() ) );
530}
531
533{
534 mData = Records{};
535 mHead = {};
536 mTail = {};
537}
538
540{
541 mData.resize(size);
542}
543
545 PlaybackSchedule &schedule, PlaybackSlice slice )
546{
547 auto &policy = schedule.GetPolicy();
548
549 if ( mData.empty() )
550 // Recording only. Don't fill the queue.
551 return;
552
553 // Don't check available space: assume it is enough because of coordination
554 // with RingBuffer.
555 auto index = mTail.mIndex;
556 auto time = mLastTime;
557 auto remainder = mTail.mRemainder;
558 auto space = TimeQueueGrainSize - remainder;
559 const auto size = mData.size();
560
561 // Produce advancing times
562 auto frames = slice.toProduce;
563 while ( frames >= space ) {
564 auto times = policy.AdvancedTrackTime( schedule, time, space );
565 time = times.second;
566 if (!std::isfinite(time))
567 time = times.first;
568 index = (index + 1) % size;
569 mData[ index ].timeValue = time;
570 frames -= space;
571 remainder = 0;
572 space = TimeQueueGrainSize;
573 }
574 // Last odd lot
575 if ( frames > 0 ) {
576 auto times = policy.AdvancedTrackTime( schedule, time, frames );
577 time = times.second;
578 if (!std::isfinite(time))
579 time = times.first;
580 remainder += frames;
581 space -= frames;
582 }
583
584 // Produce constant times if there is also some silence in the slice
585 frames = slice.frames - slice.toProduce;
586 while ( frames > 0 && frames >= space ) {
587 index = (index + 1) % size;
588 mData[ index ].timeValue = time;
589 frames -= space;
590 remainder = 0;
591 space = TimeQueueGrainSize;
592 }
593
594 mLastTime = time;
595 mTail.mRemainder = remainder + frames;
596 mTail.mIndex = index;
597}
598
600{
601 return mLastTime;
602}
603
605{
606 mLastTime = time;
607}
608
609double PlaybackSchedule::TimeQueue::Consumer( size_t nSamples, double rate )
610{
611 if ( mData.empty() ) {
612 // Recording only. No scrub or playback time warp. Don't use the queue.
613 return ( mLastTime += nSamples / rate );
614 }
615
616 // Don't check available space: assume it is enough because of coordination
617 // with RingBuffer.
618 auto remainder = mHead.mRemainder;
619 auto space = TimeQueueGrainSize - remainder;
620 const auto size = mData.size();
621 if ( nSamples >= space ) {
622 remainder = 0,
623 mHead.mIndex = (mHead.mIndex + 1) % size,
624 nSamples -= space;
625 if ( nSamples >= TimeQueueGrainSize )
626 mHead.mIndex =
627 (mHead.mIndex + ( nSamples / TimeQueueGrainSize ) ) % size,
628 nSamples %= TimeQueueGrainSize;
629 }
630 mHead.mRemainder = remainder + nSamples;
631 return mData[ mHead.mIndex ].timeValue;
632}
633
635{
636 mHead = mTail = {};
637 mLastTime = time;
638 if ( !mData.empty() )
639 mData[0].timeValue = time;
640}
int min(int a, int b)
constexpr size_t TimeQueueGrainSize
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
double SolveIntegralOfInverse(double t0, double area) const
Definition: Envelope.cpp:1292
double IntegralOfInverse(double t0, double t1) const
Definition: Envelope.cpp:1229
MixerOptions::Warp WarpOptions
Definition: Mix.h:28
bool Done(PlaybackSchedule &schedule, unsigned long) override
Returns true if schedule.GetTrackTime() has reached the end of playback.
void Initialize(PlaybackSchedule &schedule, double rate) override
Called before starting an audio stream.
MessageBuffer< SlotData > mMessageChannel
~NewDefaultPlaybackPolicy() override
Mixer::WarpOptions MixerWarpOptions(PlaybackSchedule &schedule) override
Options to use when constructing mixers for each playback track.
AudacityProject & mProject
bool Looping(const PlaybackSchedule &) const override
PlaybackSlice GetPlaybackSlice(PlaybackSchedule &schedule, size_t available) override
Choose length of one fetch of samples from tracks in a call to AudioIO::FillPlayBuffers.
std::pair< double, double > AdvancedTrackTime(PlaybackSchedule &schedule, double trackTime, size_t nSamples) override
Compute a new point in a track's timeline from an old point and a real duration.
Observer::Subscription mSubscription
bool RevertToOldDefault(const PlaybackSchedule &schedule) const
bool RepositionPlayback(PlaybackSchedule &schedule, const Mixers &playbackMixers, size_t frames, size_t available) override
AudioIO::FillPlayBuffers calls this to update its cursors into tracks for changes of position or spee...
NewDefaultPlaybackPolicy(AudacityProject &project, double trackEndTime, double loopEndTime, bool loopEnabled, bool variableSpeed)
void OnPlayRegionChange(Observer::Message)
BufferTimes SuggestedBufferTimes(PlaybackSchedule &schedule) override
Provide hints for construction of playback RingBuffer objects.
void OnPlaySpeedChange(wxCommandEvent &evt)
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
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 OffsetTrackTime(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.GetTrackTime() has reached the end of playback.
virtual ~PlaybackPolicy()=0
virtual std::chrono::milliseconds SleepInterval(PlaybackSchedule &schedule)
How long to wait between calls to AudioIO::TrackBufferExchange.
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
double GetPlaySpeed() const
static ProjectAudioIO & Get(AudacityProject &project)
PlayRegion playRegion
Definition: ViewInfo.h:217
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
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:74
const BoundedEnvelope * envelope
Definition: AudioIOBase.h:56
Immutable structure is an argument to Mixer's constructor.
Definition: MixerOptions.h:53
Default message type for Publisher.
Definition: Observer.h:26
Times are in seconds.
std::unique_ptr< PlaybackPolicy > mpPlaybackPolicy
double RealTimeRemaining() const
double GetTrackTime() const
Get current track time value, unadjusted.
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
class PlaybackSchedule::TimeQueue mTimeQueue
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 SetTrackTime(double time)
Set 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.