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, std::optional<double> pStartTime,
18 bool loopEnabled, bool variableSpeed )
19 : mProject{ project }
20 , mTrackEndTime{ trackEndTime }
21 , mLoopEndTime{ loopEndTime }
22 , mpStartTime{ pStartTime }
23 , mLoopEnabled{ loopEnabled }
24 , mVariableSpeed{ variableSpeed }
25{}
26
28
30 PlaybackSchedule &schedule, double rate )
31{
32 PlaybackPolicy::Initialize(schedule, rate);
35 schedule.mT0, mLoopEndTime, mLoopEnabled } );
36
37 auto callback = [this](auto&){ WriteMessage(); };
42}
43
45 PlaybackSchedule &schedule)
46{
48 // Enable variable rate mixing
49 return Mixer::WarpOptions(0.01, 32.0, GetPlaySpeed());
50 else
51 return PlaybackPolicy::MixerWarpOptions(schedule);
52}
53
56{
57 // Shorter times than in the default policy so that responses to changes of
58 // loop region or speed slider don't lag too much
59 using namespace std::chrono;
60 return { 0.05s, 0.05s, 0.25s };
61}
62
64{
65 return !mLoopEnabled ||
66 // Even if loop is enabled, ignore it if right of looping region
68}
69
71 PlaybackSchedule &schedule, unsigned long outputFrames )
72{
73 if (RevertToOldDefault(schedule)) {
74 auto diff = schedule.GetSequenceTime() - schedule.mT1;
75 if (schedule.ReversedTime())
76 diff *= -1;
77 return sampleCount(floor(diff * mRate + 0.5)) >= 0;
78 }
79 return false;
80}
81
83 PlaybackSchedule& schedule, double offset)
84{
85 auto time = schedule.GetSequenceTime();
86
87 // Assuming that mpStartTime always has a value when this policy is used
88 if (mpStartTime) {
89 if (mLoopEnabled) {
90 if (time < schedule.mT0)
91 time = std::clamp(time + offset, *mpStartTime, schedule.mT1);
92 else
93 time = std::clamp(time + offset, schedule.mT0, schedule.mT1);
94 }
95 else {
96 // this includes the case where the start time is after the
97 // looped region, and mLoopEnabled is set to false
98 time = std::clamp(time + offset, *mpStartTime, schedule.mT1);
99 }
100 }
101
102 schedule.RealTimeInit(time);
103 return time;
104}
105
108 PlaybackSchedule &schedule, size_t available)
109{
110 // How many samples to produce for each channel.
111 const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
112 mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
113
114 auto frames = available;
115 auto toProduce = frames;
116 double deltat = (frames / mRate) * mLastPlaySpeed;
117
118 if (deltat > realTimeRemaining) {
119 toProduce = frames = 0.5 + (realTimeRemaining * mRate) / mLastPlaySpeed;
120 auto realTime = realTimeRemaining;
121 double extra = 0;
122 if (RevertToOldDefault(schedule)) {
123 // Produce some extra silence so that the time queue consumer can
124 // satisfy its end condition
125 const double extraRealTime =
127 extra = std::min( extraRealTime, deltat - realTimeRemaining );
128 frames = ((realTimeRemaining + extra) * mRate) / mLastPlaySpeed;
129 }
130 schedule.RealTimeAdvance( realTimeRemaining + extra );
131 }
132 else
133 schedule.RealTimeAdvance( deltat );
134
135 // Don't fall into an infinite loop, if loop-playing a selection
136 // that is so short, it has no samples: detect that case
137 if (frames == 0) {
138 bool progress = (schedule.mWarpedTime != 0.0);
139 if (!progress)
140 // Cause FillPlayBuffers to make progress, filling all available with 0
141 frames = available, toProduce = 0;
142 }
143 return { available, frames, toProduce };
144}
145
147 PlaybackSchedule &schedule, double trackTime, size_t nSamples )
148{
149 bool revert = RevertToOldDefault(schedule);
150 if (!mVariableSpeed && revert)
151 return PlaybackPolicy::AdvancedTrackTime(schedule, trackTime, nSamples);
152
153 mRemaining -= std::min(mRemaining, nSamples);
154 if ( mRemaining == 0 && !revert )
155 // Wrap to start
156 return { schedule.mT1, schedule.mT0 };
157
158 // Defense against cases that might cause loops not to terminate
159 if ( fabs(schedule.mT0 - schedule.mT1) < 1e-9 )
160 return {schedule.mT0, schedule.mT0};
161
162 auto realDuration = (nSamples / mRate) * mLastPlaySpeed;
163 if (schedule.ReversedTime())
164 realDuration *= -1.0;
165
166 if (schedule.mEnvelope)
167 trackTime =
168 schedule.SolveWarpedLength(trackTime, realDuration);
169 else
170 trackTime += realDuration;
171
172 return { trackTime, trackTime };
173}
174
176 PlaybackSchedule &schedule, const Mixers &playbackMixers,
177 size_t frames, size_t available )
178{
179 // This executes in the SequenceBufferExchange thread
180 auto data = mMessageChannel.Read();
181
182 bool speedChange = false;
183 if (mVariableSpeed) {
184 speedChange = (mLastPlaySpeed != data.mPlaySpeed);
185 mLastPlaySpeed = data.mPlaySpeed;
186 }
187
188 bool empty = (data.mT0 >= data.mT1);
189 bool kicked = false;
190
191 // Amount in seconds by which right boundary can be moved left of the play
192 // head, yet loop play in progress will still capture the head
193 constexpr auto allowance = 0.5;
194
195 // Looping may become enabled if the main thread said so, but require too
196 // that the loop region is non-empty and the play head is not far to its
197 // right
198 bool loopWasEnabled = !RevertToOldDefault(schedule);
199 mLoopEnabled = data.mLoopEnabled && !empty &&
200 schedule.mTimeQueue.GetLastTime() <= data.mT1 + allowance;
201
202 // Four cases: looping transitions off, or transitions on, or stays on,
203 // or stays off.
204 // Besides which, the variable speed slider may have changed.
205
206 // If looping transitions on, or remains on and the region changed,
207 // adjust the schedule...
208 auto mine = std::tie(schedule.mT0, mLoopEndTime);
209 auto theirs = std::tie(data.mT0, data.mT1);
210 if ((loopWasEnabled != mLoopEnabled) || (mLoopEnabled && mine != theirs))
211 {
212 kicked = true;
213 if (!empty) {
214 mine = theirs;
215 schedule.mT1 = data.mT1;
216 }
217 if (!mLoopEnabled)
218 // Continue play to the end
219 schedule.mT1 = std::max(schedule.mT0, mTrackEndTime);
220 schedule.mWarpedLength = schedule.RealDuration(schedule.mT1);
221
222 auto newTime = schedule.mTimeQueue.GetLastTime();
223#if 0
224 // This would make play jump forward or backward into the adjusted
225 // looping region if not already in it
226 newTime = std::clamp(newTime, schedule.mT0, schedule.mT1);
227#endif
228
229 if (newTime >= schedule.mT1 && mLoopEnabled)
230 newTime = schedule.mT0;
231
232 // So that the play head will redraw in the right place:
233 schedule.mTimeQueue.SetLastTime(newTime);
234
235 schedule.RealTimeInit(newTime);
236 const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
237 mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
238 }
239 else if (speedChange)
240 // Don't return early
241 kicked = true;
242 else {
243 // ... else the region did not change, or looping is now off, in
244 // which case we have nothing special to do
245 if (RevertToOldDefault(schedule))
246 return PlaybackPolicy::RepositionPlayback( schedule, playbackMixers,
247 frames, available);
248 }
249
250 // msmeyer: If playing looped, check if we are at the end of the buffer
251 // and if yes, restart from the beginning.
252 if (mRemaining <= 0)
253 {
254 // Looping jumps left
255 for (auto &pMixer : playbackMixers)
256 pMixer->SetTimesAndSpeed(
257 schedule.mT0, schedule.mT1, mLastPlaySpeed, true );
258 schedule.RealTimeRestart();
259 }
260 else if (kicked)
261 {
262 // Play bounds need redefinition
263 const auto time = schedule.mTimeQueue.GetLastTime();
264 for (auto &pMixer : playbackMixers) {
265 // So that the mixer will fetch the next samples from the right place:
266 pMixer->SetTimesAndSpeed( time, schedule.mT1, mLastPlaySpeed );
267 pMixer->Reposition(time, true);
268 }
269 }
270 return false;
271}
272
274{
275 return mLoopEnabled;
276}
277
279{
280 const auto &region = ViewInfo::Get( mProject ).playRegion;
281 mMessageChannel.Write( { GetPlaySpeed(),
282 region.GetStart(), region.GetEnd(), region.Active()
283 } );
284}
285
287{
288 return mVariableSpeed
290 : 1.0;
291}
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
std::optional< double > mpStartTime
~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
double OffsetSequenceTime(PlaybackSchedule &schedule, double offset) override
Called when the play head needs to jump a certain distance.
bool Looping(const PlaybackSchedule &) const override
DefaultPlaybackPolicy(AudacityProject &project, double trackEndTime, double loopEndTime, std::optional< double > pStartTime, 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.