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.GetTrackTime() - 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 TrackBufferExchange 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 ( mLoopEnabled ? (mine != theirs) : loopWasEnabled ) {
186 kicked = true;
187 if (!empty) {
188 mine = theirs;
189 schedule.mT1 = data.mT1;
190 }
191 if (!mLoopEnabled)
192 // Continue play to the end
193 schedule.mT1 = std::max(schedule.mT0, mTrackEndTime);
194 schedule.mWarpedLength = schedule.RealDuration(schedule.mT1);
195
196 auto newTime = schedule.mTimeQueue.GetLastTime();
197#if 0
198 // This would make play jump forward or backward into the adjusted
199 // looping region if not already in it
200 newTime = std::clamp(newTime, schedule.mT0, schedule.mT1);
201#endif
202
203 if (newTime >= schedule.mT1 && mLoopEnabled)
204 newTime = schedule.mT0;
205
206 // So that the play head will redraw in the right place:
207 schedule.mTimeQueue.SetLastTime(newTime);
208
209 schedule.RealTimeInit(newTime);
210 const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
211 mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
212 }
213 else if (speedChange)
214 // Don't return early
215 kicked = true;
216 else {
217 // ... else the region did not change, or looping is now off, in
218 // which case we have nothing special to do
219 if (RevertToOldDefault(schedule))
220 return PlaybackPolicy::RepositionPlayback( schedule, playbackMixers,
221 frames, available);
222 }
223
224 // msmeyer: If playing looped, check if we are at the end of the buffer
225 // and if yes, restart from the beginning.
226 if (mRemaining <= 0)
227 {
228 // Looping jumps left
229 for (auto &pMixer : playbackMixers)
230 pMixer->SetTimesAndSpeed(
231 schedule.mT0, schedule.mT1, mLastPlaySpeed, true );
232 schedule.RealTimeRestart();
233 }
234 else if (kicked)
235 {
236 // Play bounds need redefinition
237 const auto time = schedule.mTimeQueue.GetLastTime();
238 for (auto &pMixer : playbackMixers) {
239 // So that the mixer will fetch the next samples from the right place:
240 pMixer->SetTimesAndSpeed( time, schedule.mT1, mLastPlaySpeed );
241 pMixer->Reposition(time, true);
242 }
243 }
244 return false;
245}
246
248{
249 return mLoopEnabled;
250}
251
253{
254 const auto &region = ViewInfo::Get( mProject ).playRegion;
255 mMessageChannel.Write( { GetPlaySpeed(),
256 region.GetStart(), region.GetEnd(), region.Active()
257 } );
258}
259
261{
262 return mVariableSpeed
264 : 1.0;
265}
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: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.GetTrackTime() has reached the end of playback.
MixerOptions::Warp WarpOptions
Definition: Mix.h:28
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:220
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:54
Times are in seconds.
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.
const BoundedEnvelope * mEnvelope
double RealDuration(double trackTime1) const
void RealTimeInit(double trackTime)
void RealTimeAdvance(double increment)
class AUDIO_IO_API PlaybackSchedule::TimeQueue mTimeQueue
Describes an amount of contiguous (but maybe time-warped) data to be extracted from tracks to play.