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 
23 PlaybackPolicy::~PlaybackPolicy() = default;
24 
Initialize(PlaybackSchedule &,double rate)25 void PlaybackPolicy::Initialize( PlaybackSchedule &, double rate )
26 {
27    mRate = rate;
28 }
29 
Finalize(PlaybackSchedule &)30 void PlaybackPolicy::Finalize( PlaybackSchedule & ){}
31 
MixerWarpOptions(PlaybackSchedule & schedule)32 Mixer::WarpOptions PlaybackPolicy::MixerWarpOptions(PlaybackSchedule &schedule)
33 {
34    return Mixer::WarpOptions{ schedule.mEnvelope };
35 }
36 
37 PlaybackPolicy::BufferTimes
SuggestedBufferTimes(PlaybackSchedule &)38 PlaybackPolicy::SuggestedBufferTimes(PlaybackSchedule &)
39 {
40    return { 4.0, 4.0, 10.0 };
41 }
42 
AllowSeek(PlaybackSchedule &)43 bool PlaybackPolicy::AllowSeek(PlaybackSchedule &)
44 {
45    return true;
46 }
47 
Done(PlaybackSchedule & schedule,unsigned long outputFrames)48 bool PlaybackPolicy::Done( PlaybackSchedule &schedule,
49    unsigned long outputFrames)
50 {
51    // Called from portAudio thread, use GetTrackTime()
52    auto diff = schedule.GetTrackTime() - schedule.mT1;
53    if (schedule.ReversedTime())
54       diff *= -1;
55    return sampleCount(floor(diff * mRate + 0.5)) >= 0 &&
56       // Require also that output frames are all consumed from ring buffer
57       outputFrames == 0;
58 }
59 
OffsetTrackTime(PlaybackSchedule & schedule,double offset)60 double PlaybackPolicy::OffsetTrackTime(
61    PlaybackSchedule &schedule, double offset )
62 {
63    const auto time = schedule.GetTrackTime() + offset;
64    schedule.RealTimeInit( time );
65    return time;
66 }
67 
SleepInterval(PlaybackSchedule &)68 std::chrono::milliseconds PlaybackPolicy::SleepInterval(PlaybackSchedule &)
69 {
70    using namespace std::chrono;
71    return 10ms;
72 }
73 
74 PlaybackSlice
GetPlaybackSlice(PlaybackSchedule & schedule,size_t available)75 PlaybackPolicy::GetPlaybackSlice(PlaybackSchedule &schedule, size_t available)
76 {
77    // How many samples to produce for each channel.
78    const auto realTimeRemaining = schedule.RealTimeRemaining();
79    auto frames = available;
80    auto toProduce = frames;
81    double deltat = frames / mRate;
82 
83    if (deltat > realTimeRemaining)
84    {
85       // Produce some extra silence so that the time queue consumer can
86       // satisfy its end condition
87       const double extraRealTime = (TimeQueueGrainSize + 1) / mRate;
88       auto extra = std::min( extraRealTime, deltat - realTimeRemaining );
89       auto realTime = realTimeRemaining + extra;
90       frames = realTime * mRate;
91       toProduce = realTimeRemaining * mRate;
92       schedule.RealTimeAdvance( realTime );
93    }
94    else
95       schedule.RealTimeAdvance( deltat );
96 
97    return { available, frames, toProduce };
98 }
99 
100 std::pair<double, double>
AdvancedTrackTime(PlaybackSchedule & schedule,double trackTime,size_t nSamples)101 PlaybackPolicy::AdvancedTrackTime( PlaybackSchedule &schedule,
102    double trackTime, size_t nSamples )
103 {
104    auto realDuration = nSamples / mRate;
105    if (schedule.ReversedTime())
106       realDuration *= -1.0;
107 
108    if (schedule.mEnvelope)
109       trackTime =
110          schedule.SolveWarpedLength(trackTime, realDuration);
111    else
112       trackTime += realDuration;
113 
114    if ( trackTime >= schedule.mT1 )
115       return { schedule.mT1, std::numeric_limits<double>::infinity() };
116    else
117       return { trackTime, trackTime };
118 }
119 
RepositionPlayback(PlaybackSchedule &,const Mixers &,size_t,size_t)120 bool PlaybackPolicy::RepositionPlayback(
121    PlaybackSchedule &, const Mixers &, size_t, size_t)
122 {
123    return true;
124 }
125 
Looping(const PlaybackSchedule &) const126 bool PlaybackPolicy::Looping(const PlaybackSchedule &) const
127 {
128    return false;
129 }
130 
131 namespace {
132 //! The old default playback policy plays once and consumes no messages
133 struct OldDefaultPlaybackPolicy final : PlaybackPolicy {
134    ~OldDefaultPlaybackPolicy() override = default;
135 };
136 }
137 
GetPolicy()138 PlaybackPolicy &PlaybackSchedule::GetPolicy()
139 {
140    if (mPolicyValid.load(std::memory_order_acquire) && mpPlaybackPolicy)
141       return *mpPlaybackPolicy;
142 
143    static OldDefaultPlaybackPolicy defaultPolicy;
144    return defaultPolicy;
145 }
146 
GetPolicy() const147 const PlaybackPolicy &PlaybackSchedule::GetPolicy() const
148 {
149    return const_cast<PlaybackSchedule&>(*this).GetPolicy();
150 }
151 
NewDefaultPlaybackPolicy(AudacityProject & project,double trackEndTime,double loopEndTime,bool loopEnabled,bool variableSpeed)152 NewDefaultPlaybackPolicy::NewDefaultPlaybackPolicy( AudacityProject &project,
153    double trackEndTime, double loopEndTime,
154    bool loopEnabled, bool variableSpeed )
155    : mProject{ project }
156    , mTrackEndTime{ trackEndTime }
157    , mLoopEndTime{ loopEndTime }
158    , mLoopEnabled{ loopEnabled }
159    , mVariableSpeed{ variableSpeed }
160 {}
161 
162 NewDefaultPlaybackPolicy::~NewDefaultPlaybackPolicy() = default;
163 
Initialize(PlaybackSchedule & schedule,double rate)164 void NewDefaultPlaybackPolicy::Initialize(
165    PlaybackSchedule &schedule, double rate )
166 {
167    PlaybackPolicy::Initialize(schedule, rate);
168    mLastPlaySpeed = GetPlaySpeed();
169    mMessageChannel.Write( { mLastPlaySpeed,
170       schedule.mT0, mLoopEndTime, mLoopEnabled } );
171 
172    ViewInfo::Get( mProject ).playRegion.Bind( EVT_PLAY_REGION_CHANGE,
173       &NewDefaultPlaybackPolicy::OnPlayRegionChange, this);
174    if (mVariableSpeed)
175       mProject.Bind( EVT_PLAY_SPEED_CHANGE,
176          &NewDefaultPlaybackPolicy::OnPlaySpeedChange, this);
177 }
178 
MixerWarpOptions(PlaybackSchedule & schedule)179 Mixer::WarpOptions NewDefaultPlaybackPolicy::MixerWarpOptions(
180    PlaybackSchedule &schedule)
181 {
182    if (mVariableSpeed)
183       // Enable variable rate mixing
184       return Mixer::WarpOptions(0.01, 32.0, GetPlaySpeed());
185    else
186       return PlaybackPolicy::MixerWarpOptions(schedule);
187 }
188 
189 PlaybackPolicy::BufferTimes
SuggestedBufferTimes(PlaybackSchedule &)190 NewDefaultPlaybackPolicy::SuggestedBufferTimes(PlaybackSchedule &)
191 {
192    // Shorter times than in the default policy so that responses to changes of
193    // loop region or speed slider don't lag too much
194    return { 0.05, 0.05, 0.25 };
195 }
196 
RevertToOldDefault(const PlaybackSchedule & schedule) const197 bool NewDefaultPlaybackPolicy::RevertToOldDefault(const PlaybackSchedule &schedule) const
198 {
199    return !mLoopEnabled ||
200       // Even if loop is enabled, ignore it if right of looping region
201       schedule.mTimeQueue.GetLastTime() > mLoopEndTime;
202 }
203 
Done(PlaybackSchedule & schedule,unsigned long outputFrames)204 bool NewDefaultPlaybackPolicy::Done(
205    PlaybackSchedule &schedule, unsigned long outputFrames )
206 {
207    if (RevertToOldDefault(schedule)) {
208       auto diff = schedule.GetTrackTime() - schedule.mT1;
209       if (schedule.ReversedTime())
210          diff *= -1;
211       return sampleCount(floor(diff * mRate + 0.5)) >= 0;
212    }
213    return false;
214 }
215 
216 PlaybackSlice
GetPlaybackSlice(PlaybackSchedule & schedule,size_t available)217 NewDefaultPlaybackPolicy::GetPlaybackSlice(
218    PlaybackSchedule &schedule, size_t available)
219 {
220    // How many samples to produce for each channel.
221    const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
222    mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
223 
224    auto frames = available;
225    auto toProduce = frames;
226    double deltat = (frames / mRate) * mLastPlaySpeed;
227 
228    if (deltat > realTimeRemaining) {
229       toProduce = frames = (realTimeRemaining * mRate) / mLastPlaySpeed;
230       auto realTime = realTimeRemaining;
231       double extra = 0;
232       if (RevertToOldDefault(schedule)) {
233          // Produce some extra silence so that the time queue consumer can
234          // satisfy its end condition
235          const double extraRealTime =
236             ((TimeQueueGrainSize + 1) / mRate) * mLastPlaySpeed;
237          auto extra = std::min( extraRealTime, deltat - realTimeRemaining );
238          frames = ((realTimeRemaining + extra) * mRate) / mLastPlaySpeed;
239       }
240       schedule.RealTimeAdvance( realTimeRemaining + extra );
241    }
242    else
243       schedule.RealTimeAdvance( deltat );
244 
245    // Don't fall into an infinite loop, if loop-playing a selection
246    // that is so short, it has no samples: detect that case
247    if (frames == 0) {
248       bool progress = (schedule.mWarpedTime != 0.0);
249       if (!progress)
250          // Cause FillPlayBuffers to make progress, filling all available with 0
251          frames = available, toProduce = 0;
252    }
253    return { available, frames, toProduce };
254 }
255 
AdvancedTrackTime(PlaybackSchedule & schedule,double trackTime,size_t nSamples)256 std::pair<double, double> NewDefaultPlaybackPolicy::AdvancedTrackTime(
257    PlaybackSchedule &schedule, double trackTime, size_t nSamples )
258 {
259    bool revert = RevertToOldDefault(schedule);
260    if (!mVariableSpeed && revert)
261       return PlaybackPolicy::AdvancedTrackTime(schedule, trackTime, nSamples);
262 
263    mRemaining -= std::min(mRemaining, nSamples);
264    if ( mRemaining == 0 && !revert )
265       // Wrap to start
266       return { schedule.mT1, schedule.mT0 };
267 
268    // Defense against cases that might cause loops not to terminate
269    if ( fabs(schedule.mT0 - schedule.mT1) < 1e-9 )
270       return {schedule.mT0, schedule.mT0};
271 
272    auto realDuration = (nSamples / mRate) * mLastPlaySpeed;
273    if (schedule.ReversedTime())
274       realDuration *= -1.0;
275 
276    if (schedule.mEnvelope)
277       trackTime =
278          schedule.SolveWarpedLength(trackTime, realDuration);
279    else
280       trackTime += realDuration;
281 
282    return { trackTime, trackTime };
283 }
284 
RepositionPlayback(PlaybackSchedule & schedule,const Mixers & playbackMixers,size_t frames,size_t available)285 bool NewDefaultPlaybackPolicy::RepositionPlayback(
286    PlaybackSchedule &schedule, const Mixers &playbackMixers,
287    size_t frames, size_t available )
288 {
289    // This executes in the TrackBufferExchange thread
290    auto data = mMessageChannel.Read();
291 
292    bool speedChange = false;
293    if (mVariableSpeed) {
294       speedChange = (mLastPlaySpeed != data.mPlaySpeed);
295       mLastPlaySpeed = data.mPlaySpeed;
296    }
297 
298    bool empty = (data.mT0 >= data.mT1);
299    bool kicked = false;
300 
301    // Amount in seconds by which right boundary can be moved left of the play
302    // head, yet loop play in progress will still capture the head
303    constexpr auto allowance = 0.5;
304 
305    // Looping may become enabled if the main thread said so, but require too
306    // that the loop region is non-empty and the play head is not far to its
307    // right
308    bool loopWasEnabled = !RevertToOldDefault(schedule);
309    mLoopEnabled = data.mLoopEnabled && !empty &&
310       schedule.mTimeQueue.GetLastTime() <= data.mT1 + allowance;
311 
312    // Four cases:  looping transitions off, or transitions on, or stays on,
313    // or stays off.
314    // Besides which, the variable speed slider may have changed.
315 
316    // If looping transitions on, or remains on and the region changed,
317    // adjust the schedule...
318    auto mine = std::tie(schedule.mT0, mLoopEndTime);
319    auto theirs = std::tie(data.mT0, data.mT1);
320    if ( mLoopEnabled ? (mine != theirs) : loopWasEnabled ) {
321       kicked = true;
322       if (!empty) {
323          mine = theirs;
324          schedule.mT1 = data.mT1;
325       }
326       if (!mLoopEnabled)
327          // Continue play to the end
328          schedule.mT1 = std::max(schedule.mT0, mTrackEndTime);
329       schedule.mWarpedLength = schedule.RealDuration(schedule.mT1);
330 
331       auto newTime = schedule.mTimeQueue.GetLastTime();
332 #if 0
333       // This would make play jump forward or backward into the adjusted
334       // looping region if not already in it
335       newTime = std::clamp(newTime, schedule.mT0, schedule.mT1);
336 #endif
337 
338       if (newTime >= schedule.mT1 && mLoopEnabled)
339          newTime = schedule.mT0;
340 
341       // So that the play head will redraw in the right place:
342       schedule.mTimeQueue.SetLastTime(newTime);
343 
344       schedule.RealTimeInit(newTime);
345       const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
346       mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
347    }
348    else if (speedChange)
349       // Don't return early
350       kicked = true;
351    else {
352       // ... else the region did not change, or looping is now off, in
353       // which case we have nothing special to do
354       if (RevertToOldDefault(schedule))
355          return PlaybackPolicy::RepositionPlayback( schedule, playbackMixers,
356             frames, available);
357    }
358 
359    // msmeyer: If playing looped, check if we are at the end of the buffer
360    // and if yes, restart from the beginning.
361    if (mRemaining <= 0)
362    {
363       // Looping jumps left
364       for (auto &pMixer : playbackMixers)
365          pMixer->SetTimesAndSpeed(
366             schedule.mT0, schedule.mT1, mLastPlaySpeed, true );
367       schedule.RealTimeRestart();
368    }
369    else if (kicked)
370    {
371       // Play bounds need redefinition
372       const auto time = schedule.mTimeQueue.GetLastTime();
373       for (auto &pMixer : playbackMixers) {
374          // So that the mixer will fetch the next samples from the right place:
375          pMixer->SetTimesAndSpeed( time, schedule.mT1, mLastPlaySpeed );
376          pMixer->Reposition(time, true);
377       }
378    }
379    return false;
380 }
381 
Looping(const PlaybackSchedule &) const382 bool NewDefaultPlaybackPolicy::Looping( const PlaybackSchedule & ) const
383 {
384    return true;
385 }
386 
OnPlayRegionChange(PlayRegionEvent & evt)387 void NewDefaultPlaybackPolicy::OnPlayRegionChange( PlayRegionEvent &evt)
388 {
389    // This executes in the main thread
390    evt.Skip(); // Let other listeners hear the event too
391    WriteMessage();
392 }
393 
OnPlaySpeedChange(wxCommandEvent & evt)394 void NewDefaultPlaybackPolicy::OnPlaySpeedChange(wxCommandEvent &evt)
395 {
396    evt.Skip(); // Let other listeners hear the event too
397    WriteMessage();
398 }
399 
WriteMessage()400 void NewDefaultPlaybackPolicy::WriteMessage()
401 {
402    const auto &region = ViewInfo::Get( mProject ).playRegion;
403    mMessageChannel.Write( { GetPlaySpeed(),
404       region.GetStart(), region.GetEnd(), region.Active()
405    } );
406 }
407 
GetPlaySpeed()408 double NewDefaultPlaybackPolicy::GetPlaySpeed()
409 {
410    return mVariableSpeed
411       ? ProjectAudioIO::Get(mProject).GetPlaySpeed()
412       : 1.0;
413 }
414 
Init(const double t0,const double t1,const AudioIOStartStreamOptions & options,const RecordingSchedule * pRecordingSchedule)415 void PlaybackSchedule::Init(
416    const double t0, const double t1,
417    const AudioIOStartStreamOptions &options,
418    const RecordingSchedule *pRecordingSchedule )
419 {
420    mpPlaybackPolicy.reset();
421 
422    if ( pRecordingSchedule )
423       // It does not make sense to apply the time warp during overdub recording,
424       // which defeats the purpose of making the recording synchronized with
425       // the existing audio.  (Unless we figured out the inverse warp of the
426       // captured samples in real time.)
427       // So just quietly ignore the time track.
428       mEnvelope = nullptr;
429    else
430       mEnvelope = options.envelope;
431 
432    mT0      = t0;
433    if (pRecordingSchedule)
434       mT0 -= pRecordingSchedule->mPreRoll;
435 
436    mT1      = t1;
437    if (pRecordingSchedule)
438       // adjust mT1 so that we don't give paComplete too soon to fill up the
439       // desired length of recording
440       mT1 -= pRecordingSchedule->mLatencyCorrection;
441 
442    // Main thread's initialization of mTime
443    SetTrackTime( mT0 );
444 
445    if (options.policyFactory)
446       mpPlaybackPolicy = options.policyFactory(options);
447 
448    mWarpedTime = 0.0;
449    mWarpedLength = RealDuration(mT1);
450 
451    mPolicyValid.store(true, std::memory_order_release);
452 }
453 
ComputeWarpedLength(double t0,double t1) const454 double PlaybackSchedule::ComputeWarpedLength(double t0, double t1) const
455 {
456    if (mEnvelope)
457       return mEnvelope->IntegralOfInverse(t0, t1);
458    else
459       return t1 - t0;
460 }
461 
SolveWarpedLength(double t0,double length) const462 double PlaybackSchedule::SolveWarpedLength(double t0, double length) const
463 {
464    if (mEnvelope)
465       return mEnvelope->SolveIntegralOfInverse(t0, length);
466    else
467       return t0 + length;
468 }
469 
RealDuration(double trackTime1) const470 double PlaybackSchedule::RealDuration(double trackTime1) const
471 {
472    return fabs(RealDurationSigned(trackTime1));
473 }
474 
RealDurationSigned(double trackTime1) const475 double PlaybackSchedule::RealDurationSigned(double trackTime1) const
476 {
477    return ComputeWarpedLength(mT0, trackTime1);
478 }
479 
RealTimeRemaining() const480 double PlaybackSchedule::RealTimeRemaining() const
481 {
482    return mWarpedLength - mWarpedTime;
483 }
484 
RealTimeAdvance(double increment)485 void PlaybackSchedule::RealTimeAdvance( double increment )
486 {
487    mWarpedTime += increment;
488 }
489 
RealTimeInit(double trackTime)490 void PlaybackSchedule::RealTimeInit( double trackTime )
491 {
492    mWarpedTime = RealDurationSigned( trackTime );
493 }
494 
RealTimeRestart()495 void PlaybackSchedule::RealTimeRestart()
496 {
497    mWarpedTime = 0;
498 }
499 
ToConsume() const500 double RecordingSchedule::ToConsume() const
501 {
502    return mDuration - Consumed();
503 }
504 
Consumed() const505 double RecordingSchedule::Consumed() const
506 {
507    return std::max( 0.0, mPosition + TotalCorrection() );
508 }
509 
ToDiscard() const510 double RecordingSchedule::ToDiscard() const
511 {
512    return std::max(0.0, -( mPosition + TotalCorrection() ) );
513 }
514 
Clear()515 void PlaybackSchedule::TimeQueue::Clear()
516 {
517    mData = Records{};
518    mHead = {};
519    mTail = {};
520 }
521 
Resize(size_t size)522 void PlaybackSchedule::TimeQueue::Resize(size_t size)
523 {
524    mData.resize(size);
525 }
526 
Producer(PlaybackSchedule & schedule,size_t nSamples)527 void PlaybackSchedule::TimeQueue::Producer(
528    PlaybackSchedule &schedule, size_t nSamples )
529 {
530    auto &policy = schedule.GetPolicy();
531 
532    if ( mData.empty() )
533       // Recording only.  Don't fill the queue.
534       return;
535 
536    // Don't check available space:  assume it is enough because of coordination
537    // with RingBuffer.
538    auto index = mTail.mIndex;
539    auto time = mLastTime;
540    auto remainder = mTail.mRemainder;
541    auto space = TimeQueueGrainSize - remainder;
542    const auto size = mData.size();
543 
544    while ( nSamples >= space ) {
545       auto times = policy.AdvancedTrackTime( schedule, time, space );
546       time = times.second;
547       if (!std::isfinite(time))
548          time = times.first;
549       index = (index + 1) % size;
550       mData[ index ].timeValue = time;
551       nSamples -= space;
552       remainder = 0;
553       space = TimeQueueGrainSize;
554    }
555 
556    // Last odd lot
557    if ( nSamples > 0 ) {
558       auto times = policy.AdvancedTrackTime( schedule, time, nSamples );
559       time = times.second;
560       if (!std::isfinite(time))
561          time = times.first;
562    }
563 
564    mLastTime = time;
565    mTail.mRemainder = remainder + nSamples;
566    mTail.mIndex = index;
567 }
568 
GetLastTime() const569 double PlaybackSchedule::TimeQueue::GetLastTime() const
570 {
571    return mLastTime;
572 }
573 
SetLastTime(double time)574 void PlaybackSchedule::TimeQueue::SetLastTime(double time)
575 {
576    mLastTime = time;
577 }
578 
Consumer(size_t nSamples,double rate)579 double PlaybackSchedule::TimeQueue::Consumer( size_t nSamples, double rate )
580 {
581    if ( mData.empty() ) {
582       // Recording only.  No scrub or playback time warp.  Don't use the queue.
583       return ( mLastTime += nSamples / rate );
584    }
585 
586    // Don't check available space:  assume it is enough because of coordination
587    // with RingBuffer.
588    auto remainder = mHead.mRemainder;
589    auto space = TimeQueueGrainSize - remainder;
590    const auto size = mData.size();
591    if ( nSamples >= space ) {
592       remainder = 0,
593       mHead.mIndex = (mHead.mIndex + 1) % size,
594       nSamples -= space;
595       if ( nSamples >= TimeQueueGrainSize )
596          mHead.mIndex =
597             (mHead.mIndex + ( nSamples / TimeQueueGrainSize ) ) % size,
598          nSamples %= TimeQueueGrainSize;
599    }
600    mHead.mRemainder = remainder + nSamples;
601    return mData[ mHead.mIndex ].timeValue;
602 }
603 
Prime(double time)604 void PlaybackSchedule::TimeQueue::Prime(double time)
605 {
606    mHead = mTail = {};
607    mLastTime = time;
608    if ( !mData.empty() )
609       mData[0].timeValue = time;
610 }
611