Audacity 3.2.0
DefaultPlaybackPolicy.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file DefaultPlaybackPolicy.cpp
6
7 Paul Licameli split from PlaybackSchedule.cpp
8
9 **********************************************************************/
10
12#include "ProjectAudioIO.h"
13#include "SampleCount.h"
14#include "ViewInfo.h"
15
17 double trackEndTime, double loopEndTime,
18 bool loopEnabled, bool variableSpeed )
19 : mProject{ project }
20 , mTrackEndTime{ trackEndTime }
21 , mLoopEndTime{ loopEndTime }
22 , mLoopEnabled{ loopEnabled }
23 , mVariableSpeed{ variableSpeed }
24{}
25
27
29 PlaybackSchedule &schedule, double rate )
30{
31 PlaybackPolicy::Initialize(schedule, rate);
34 schedule.mT0, mLoopEndTime, mLoopEnabled } );
35
36 auto callback = [this](auto&){ WriteMessage(); };
41}
42
44 PlaybackSchedule &schedule)
45{
47 // Enable variable rate mixing
48 return Mixer::WarpOptions(0.01, 32.0, GetPlaySpeed());
49 else
50 return PlaybackPolicy::MixerWarpOptions(schedule);
51}
52
55{
56 // Shorter times than in the default policy so that responses to changes of
57 // loop region or speed slider don't lag too much
58 using namespace std::chrono;
59 return { 0.05s, 0.05s, 0.25s };
60}
61
63{
64 return !mLoopEnabled ||
65 // Even if loop is enabled, ignore it if right of looping region
67}
68
70 PlaybackSchedule &schedule, unsigned long outputFrames )
71{
72 if (RevertToOldDefault(schedule)) {
73 auto diff = schedule.GetSequenceTime() - schedule.mT1;
74 if (schedule.ReversedTime())
75 diff *= -1;
76 return sampleCount(floor(diff * mRate + 0.5)) >= 0;
77 }
78 return false;
79}
80
83 PlaybackSchedule &schedule, size_t available)
84{
85 // How many samples to produce for each channel.
86 const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
87 mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
88
89 auto frames = available;
90 auto toProduce = frames;
91 double deltat = (frames / mRate) * mLastPlaySpeed;
92
93 if (deltat > realTimeRemaining) {
94 toProduce = frames = 0.5 + (realTimeRemaining * mRate) / mLastPlaySpeed;
95 auto realTime = realTimeRemaining;
96 double extra = 0;
97 if (RevertToOldDefault(schedule)) {
98 // Produce some extra silence so that the time queue consumer can
99 // satisfy its end condition
100 const double extraRealTime =
102 extra = std::min( extraRealTime, deltat - realTimeRemaining );
103 frames = ((realTimeRemaining + extra) * mRate) / mLastPlaySpeed;
104 }
105 schedule.RealTimeAdvance( realTimeRemaining + extra );
106 }
107 else
108 schedule.RealTimeAdvance( deltat );
109
110 // Don't fall into an infinite loop, if loop-playing a selection
111 // that is so short, it has no samples: detect that case
112 if (frames == 0) {
113 bool progress = (schedule.mWarpedTime != 0.0);
114 if (!progress)
115 // Cause FillPlayBuffers to make progress, filling all available with 0
116 frames = available, toProduce = 0;
117 }
118 return { available, frames, toProduce };
119}
120
122 PlaybackSchedule &schedule, double trackTime, size_t nSamples )
123{
124 bool revert = RevertToOldDefault(schedule);
125 if (!mVariableSpeed && revert)
126 return PlaybackPolicy::AdvancedTrackTime(schedule, trackTime, nSamples);
127
128 mRemaining -= std::min(mRemaining, nSamples);
129 if ( mRemaining == 0 && !revert )
130 // Wrap to start
131 return { schedule.mT1, schedule.mT0 };
132
133 // Defense against cases that might cause loops not to terminate
134 if ( fabs(schedule.mT0 - schedule.mT1) < 1e-9 )
135 return {schedule.mT0, schedule.mT0};
136
137 auto realDuration = (nSamples / mRate) * mLastPlaySpeed;
138 if (schedule.ReversedTime())
139 realDuration *= -1.0;
140
141 if (schedule.mEnvelope)
142 trackTime =
143 schedule.SolveWarpedLength(trackTime, realDuration);
144 else
145 trackTime += realDuration;
146
147 return { trackTime, trackTime };
148}
149
151 PlaybackSchedule &schedule, const Mixers &playbackMixers,
152 size_t frames, size_t available )
153{
154 // This executes in the SequenceBufferExchange thread
155 auto data = mMessageChannel.Read();
156
157 bool speedChange = false;
158 if (mVariableSpeed) {
159 speedChange = (mLastPlaySpeed != data.mPlaySpeed);
160 mLastPlaySpeed = data.mPlaySpeed;
161 }
162
163 bool empty = (data.mT0 >= data.mT1);
164 bool kicked = false;
165
166 // Amount in seconds by which right boundary can be moved left of the play
167 // head, yet loop play in progress will still capture the head
168 constexpr auto allowance = 0.5;
169
170 // Looping may become enabled if the main thread said so, but require too
171 // that the loop region is non-empty and the play head is not far to its
172 // right
173 bool loopWasEnabled = !RevertToOldDefault(schedule);
174 mLoopEnabled = data.mLoopEnabled && !empty &&
175 schedule.mTimeQueue.GetLastTime() <= data.mT1 + allowance;
176
177 // Four cases: looping transitions off, or transitions on, or stays on,
178 // or stays off.
179 // Besides which, the variable speed slider may have changed.
180
181 // If looping transitions on, or remains on and the region changed,
182 // adjust the schedule...
183 auto mine = std::tie(schedule.mT0, mLoopEndTime);
184 auto theirs = std::tie(data.mT0, data.mT1);
185 if ((loopWasEnabled != mLoopEnabled) || (mLoopEnabled && mine != theirs))
186 {
187 kicked = true;
188 if (!empty) {
189 mine = theirs;
190 schedule.mT1 = data.mT1;
191 }
192 if (!mLoopEnabled)
193 // Continue play to the end
194 schedule.mT1 = std::max(schedule.mT0, mTrackEndTime);
195 schedule.mWarpedLength = schedule.RealDuration(schedule.mT1);
196
197 auto newTime = schedule.mTimeQueue.GetLastTime();
198#if 0
199 // This would make play jump forward or backward into the adjusted
200 // looping region if not already in it
201 newTime = std::clamp(newTime, schedule.mT0, schedule.mT1);
202#endif
203
204 if (newTime >= schedule.mT1 && mLoopEnabled)
205 newTime = schedule.mT0;
206
207 // So that the play head will redraw in the right place:
208 schedule.mTimeQueue.SetLastTime(newTime);
209
210 schedule.RealTimeInit(newTime);
211 const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
212 mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
213 }
214 else if (speedChange)
215 // Don't return early
216 kicked = true;
217 else {
218 // ... else the region did not change, or looping is now off, in
219 // which case we have nothing special to do
220 if (RevertToOldDefault(schedule))
221 return PlaybackPolicy::RepositionPlayback( schedule, playbackMixers,
222 frames, available);
223 }
224
225 // msmeyer: If playing looped, check if we are at the end of the buffer
226 // and if yes, restart from the beginning.
227 if (mRemaining <= 0)
228 {
229 // Looping jumps left
230 for (auto &pMixer : playbackMixers)
231 pMixer->SetTimesAndSpeed(
232 schedule.mT0, schedule.mT1, mLastPlaySpeed, true );
233 schedule.RealTimeRestart();
234 }
235 else if (kicked)
236 {
237 // Play bounds need redefinition
238 const auto time = schedule.mTimeQueue.GetLastTime();
239 for (auto &pMixer : playbackMixers) {
240 // So that the mixer will fetch the next samples from the right place:
241 pMixer->SetTimesAndSpeed( time, schedule.mT1, mLastPlaySpeed );
242 pMixer->Reposition(time, true);
243 }
244 }
245 return false;
246}
247
249{
250 return mLoopEnabled;
251}
252
254{
255 const auto &region = ViewInfo::Get( mProject ).playRegion;
256 mMessageChannel.Write( { GetPlaySpeed(),
257 region.GetStart(), region.GetEnd(), region.Active()
258 } );
259}
260
262{
263 return mVariableSpeed
265 : 1.0;
266}
int min(int a, int b)
constexpr size_t TimeQueueGrainSize
const auto project
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
PlaybackSlice GetPlaybackSlice(PlaybackSchedule &schedule, size_t available) override
Choose length of one fetch of samples from tracks in a call to AudioIO::FillPlayBuffers.
BufferTimes SuggestedBufferTimes(PlaybackSchedule &schedule) override
Provide hints for construction of playback RingBuffer objects.
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...
void Initialize(PlaybackSchedule &schedule, double rate) override
Called before starting an audio stream.
MessageBuffer< SlotData > mMessageChannel
Observer::Subscription mSpeedSubscription
bool RevertToOldDefault(const PlaybackSchedule &schedule) const
Mixer::WarpOptions MixerWarpOptions(PlaybackSchedule &schedule) override
Options to use when constructing mixers for each playback track.
AudacityProject & mProject
~DefaultPlaybackPolicy() override
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 mRegionSubscription
bool Looping(const PlaybackSchedule &) const override
DefaultPlaybackPolicy(AudacityProject &project, double trackEndTime, double loopEndTime, bool loopEnabled, bool variableSpeed)
bool Done(PlaybackSchedule &schedule, unsigned long) override
Returns true if schedule.GetSequenceTime() has reached the end of playback.
MixerOptions::Warp WarpOptions
Definition: Mix.h:29
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
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.
std::vector< std::unique_ptr< Mixer > > Mixers
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...
double GetLastTime() const
Return the last time saved by Producer.
double GetPlaySpeed() const
static ProjectAudioIO & Get(AudacityProject &project)
PlayRegion playRegion
Definition: ViewInfo.h:216
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
Immutable structure is an argument to Mixer's constructor.
Definition: MixerOptions.h:56
Times are in seconds.
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.
const BoundedEnvelope * mEnvelope
double RealDuration(double trackTime1) const
void RealTimeInit(double trackTime)
void RealTimeAdvance(double increment)
class AUDIO_IO_API PlaybackSchedule::TimeQueue mTimeQueue
double GetSequenceTime() const
Get current track time value, unadjusted.
Describes an amount of contiguous (but maybe time-warped) data to be extracted from tracks to play.