1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-20 06:10:06 +02:00

Some simplifications and renamings related to ScrubState

This commit is contained in:
Paul Licameli 2018-08-26 09:47:43 -04:00
commit bdf1cb32fe
4 changed files with 104 additions and 193 deletions

View File

@ -522,33 +522,16 @@ constexpr size_t TimeQueueGrainSize = 2000;
#endif #endif
/* struct AudioIO::ScrubState
This work queue class coordinates two threads during scrub play:
The UI thread which specifies scrubbing intervals to play,
and the Audio thread which consumes those specifications
and fills the ring buffers with samples for play (to be consumed by yet another
thread, spawned by PortAudio).
Audio produces samples for PortAudio, which consumes them, both in
approximate real time. The UI thread might go idle and so Audio
might catch up, emptying the queue and causing scrub to go silent.
The UI thread will not normally outrun Audio -- because InitEntry()
limits the real time duration over which each enqueued interval will play.
So a small, fixed queue size should be adequate.
*/
struct AudioIO::ScrubQueue
{ {
ScrubQueue(double t0, double t1, wxLongLong startClockMillis, ScrubState(double t0, double t1, wxLongLong startClockMillis,
double rate, double maxDebt, double rate,
const ScrubbingOptions &options) const ScrubbingOptions &options)
: mTrailingIdx(0) : mTrailingIdx(0)
, mMiddleIdx(1) , mMiddleIdx(1)
, mLeadingIdx(1) , mLeadingIdx(1)
, mRate(rate) , mRate(rate)
, mLastScrubTimeMillis(startClockMillis) , mLastScrubTimeMillis(startClockMillis)
, mMaxDebt { lrint(maxDebt * rate) }
, mUpdating() , mUpdating()
{ {
const sampleCount s0 { llrint( mRate * const sampleCount s0 { llrint( mRate *
@ -565,15 +548,6 @@ struct AudioIO::ScrubQueue
dd.Cancel(); dd.Cancel();
} }
} }
~ScrubQueue() {}
double LastTimeInQueue() const
{
// Needed by the main thread sometimes
wxMutexLocker locker(mUpdating);
const Entry &previous = mEntries[(mLeadingIdx + Size - 1) % Size];
return previous.mS1.as_double() / mRate;
}
// This is for avoiding deadlocks while starting a scrub: // This is for avoiding deadlocks while starting a scrub:
// Audio stream needs to be unblocked // Audio stream needs to be unblocked
@ -584,7 +558,7 @@ struct AudioIO::ScrubQueue
mAvailable.Signal(); mAvailable.Signal();
} }
bool Producer(double end, const ScrubbingOptions &options) bool Update(double end, const ScrubbingOptions &options)
{ {
// Main thread indicates a scrubbing interval // Main thread indicates a scrubbing interval
@ -601,18 +575,16 @@ struct AudioIO::ScrubQueue
// Use the previous end as NEW start. // Use the previous end as NEW start.
const auto s0 = previous->mS1; const auto s0 = previous->mS1;
Duration dd { *this }; Duration dd { *this };
const auto &origDuration = dd.duration; if (dd.duration <= 0)
if (origDuration <= 0)
return false; return false;
auto actualDuration = origDuration; const sampleCount s1 ( options.bySpeed
const sampleCount s1 ( options.enqueueBySpeed
? s0.as_double() + ? s0.as_double() +
lrint(origDuration.as_double() * end) // end is a speed lrint(dd.duration.as_double() * end) // end is a speed
: lrint(end * mRate) // end is a time : lrint(end * mRate) // end is a time
); );
auto success = auto success =
current->Init(previous, s0, s1, actualDuration, options, mRate); current->Init(previous, s0, s1, dd.duration, options, mRate);
if (success) if (success)
mLeadingIdx = next; mLeadingIdx = next;
else { else {
@ -620,21 +592,6 @@ struct AudioIO::ScrubQueue
return false; return false;
} }
// Fill up the queue with some silence if there was trimming
wxASSERT(actualDuration <= origDuration);
if (actualDuration < origDuration) {
next = (mLeadingIdx + 1) % Size;
if (next != mTrailingIdx) {
previous = &mEntries[(mLeadingIdx + Size - 1) % Size];
current = &mEntries[mLeadingIdx];
current->InitSilent(*previous, origDuration - actualDuration);
mLeadingIdx = next;
}
else
// Oops, can't enqueue the silence -- so do what?
;
}
mAvailable.Signal(); mAvailable.Signal();
return result; return result;
} }
@ -649,7 +606,7 @@ struct AudioIO::ScrubQueue
} }
} }
void Consumer(sampleCount &startSample, sampleCount &endSample, void Get(sampleCount &startSample, sampleCount &endSample,
sampleCount &duration, sampleCount &duration,
Maybe<wxMutexLocker> &cleanup) Maybe<wxMutexLocker> &cleanup)
{ {
@ -657,12 +614,8 @@ struct AudioIO::ScrubQueue
// MAY ADVANCE mMiddleIdx, WHICH MAY EQUAL mLeadingIdx, BUT DOES NOT PASS IT. // MAY ADVANCE mMiddleIdx, WHICH MAY EQUAL mLeadingIdx, BUT DOES NOT PASS IT.
bool checkDebt = false;
if (!cleanup) { if (!cleanup) {
cleanup.create(mUpdating); cleanup.create(mUpdating);
// Check for cancellation of work only when re-enetering the cricial section
checkDebt = true;
} }
while(!mNudged && mMiddleIdx == mLeadingIdx) while(!mNudged && mMiddleIdx == mLeadingIdx)
mAvailable.Wait(); mAvailable.Wait();
@ -671,84 +624,61 @@ struct AudioIO::ScrubQueue
auto now = ::wxGetLocalTimeMillis(); auto now = ::wxGetLocalTimeMillis();
if (checkDebt &&
mLastTransformerTimeMillis >= 0 && // Not the first time for this scrub
mMiddleIdx != mLeadingIdx) {
// There is work in the queue, but if Producer is outrunning us, discard some,
// which may make a skip yet keep playback better synchronized with user gestures.
const auto interval = (now - mLastTransformerTimeMillis).ToDouble() / 1000.0;
//const Entry &previous = mEntries[(mMiddleIdx + Size - 1) % Size];
const auto deficit =
static_cast<long>(interval * mRate) - // Samples needed in the last time interval
mCredit; // Samples done in the last time interval
mCredit = 0;
mDebt += deficit;
auto toDiscard = mDebt - mMaxDebt;
while (toDiscard > 0 && mMiddleIdx != mLeadingIdx) {
// Cancel some debt (discard some NEW work)
auto &entry = mEntries[mMiddleIdx];
auto &dur = entry.mDuration;
if (toDiscard >= dur) {
// Discard entire queue entry
mDebt -= dur;
toDiscard -= dur;
dur = 0;
mTrailingIdx = mMiddleIdx;
mMiddleIdx = (mMiddleIdx + 1) % Size;
}
else {
// Adjust the start time
auto &start = entry.mS0;
const auto end = entry.mS1;
const auto ratio = toDiscard.as_double() / dur.as_double();
const sampleCount adjustment(
std::abs((end - start).as_long_long()) * ratio
);
if (start <= end)
start += adjustment;
else
start -= adjustment;
mDebt -= toDiscard;
dur -= toDiscard;
toDiscard = 0;
}
}
}
if (mMiddleIdx != mLeadingIdx) { if (mMiddleIdx != mLeadingIdx) {
// There is still work in the queue, after cancelling debt Data &entry = mEntries[mMiddleIdx];
Entry &entry = mEntries[mMiddleIdx]; if (entry.mDuration > 0) {
// First use of the entry
startSample = entry.mS0; startSample = entry.mS0;
endSample = entry.mS1; endSample = entry.mS1;
duration = entry.mDuration; duration = entry.mDuration;
entry.mDuration = 0;
}
else if (entry.mSilence > 0) {
// Second use of the entry
startSample = endSample = entry.mS1;
duration = entry.mSilence;
entry.mSilence = 0;
}
if (entry.mSilence == 0) {
// Entry is used up
mTrailingIdx = mMiddleIdx; mTrailingIdx = mMiddleIdx;
mMiddleIdx = (mMiddleIdx + 1) % Size; mMiddleIdx = (mMiddleIdx + 1) % Size;
mCredit += duration; }
} }
else { else {
// We got the shut-down signal, or we got nudged, or we discarded all the work. // We got the shut-down signal, or we got nudged, or we discarded all the work.
startSample = endSample = duration = -1L; startSample = endSample = duration = -1L;
} }
if (checkDebt)
mLastTransformerTimeMillis = now;
} }
private: double LastTrackTime() const
struct Entry
{ {
Entry() // Needed by the main thread sometimes
wxMutexLocker locker(mUpdating);
const Data &previous = mEntries[(mLeadingIdx + Size - 1) % Size];
return previous.mS1.as_double() / mRate;
}
~ScrubState() {}
private:
struct Data
{
Data()
: mS0(0) : mS0(0)
, mS1(0) , mS1(0)
, mGoal(0) , mGoal(0)
, mDuration(0) , mDuration(0)
, mSilence(0)
{} {}
bool Init(Entry *previous, sampleCount s0, sampleCount s1, bool Init(Data *previous, sampleCount s0, sampleCount s1,
sampleCount &duration /* in/out */, sampleCount duration,
const ScrubbingOptions &options, double rate) const ScrubbingOptions &options, double rate)
{ {
auto origDuration = duration;
mSilence = 0;
const bool &adjustStart = options.adjustStart; const bool &adjustStart = options.adjustStart;
wxASSERT(duration > 0); wxASSERT(duration > 0);
@ -841,7 +771,7 @@ private:
) )
); );
if (newDuration == 0) { if (newDuration == 0) {
// Enqueue a silent scrub with s0 == s1 // A silent scrub with s0 == s1
silent = true; silent = true;
s1 = s0; s1 = s0;
} }
@ -866,31 +796,21 @@ private:
mS0 = s0; mS0 = s0;
mS1 = s1; mS1 = s1;
mDuration = duration; mDuration = duration;
if (duration < origDuration)
mSilence = origDuration - duration;
return true; return true;
} }
void InitSilent(const Entry &previous, sampleCount duration)
{
mGoal = previous.mGoal;
mS0 = mS1 = previous.mS1;
mDuration = duration;
}
// These sample counts are initialized in the UI, producer, thread:
sampleCount mS0; sampleCount mS0;
sampleCount mS1; sampleCount mS1;
sampleCount mGoal; sampleCount mGoal;
// This field is initialized in the UI thread too, and
// this work queue item corresponds to exactly this many samples of
// playback output:
sampleCount mDuration; sampleCount mDuration;
sampleCount mSilence;
// The middleman Audio thread does not change these entries, but only
// changes indices in the queue structure.
}; };
struct Duration { struct Duration {
Duration (ScrubQueue &queue_) : queue(queue_) {} Duration (ScrubState &queue_) : queue(queue_) {}
~Duration () ~Duration ()
{ {
if(!cancelled) if(!cancelled)
@ -899,7 +819,7 @@ private:
void Cancel() { cancelled = true; } void Cancel() { cancelled = true; }
ScrubQueue &queue; ScrubState &queue;
const wxLongLong clockTime { ::wxGetLocalTimeMillis() }; const wxLongLong clockTime { ::wxGetLocalTimeMillis() };
const sampleCount duration { static_cast<long long> const sampleCount duration { static_cast<long long>
(queue.mRate * (clockTime - queue.mLastScrubTimeMillis).ToDouble() / 1000.0) (queue.mRate * (clockTime - queue.mLastScrubTimeMillis).ToDouble() / 1000.0)
@ -908,18 +828,13 @@ private:
}; };
enum { Size = 10 }; enum { Size = 10 };
Entry mEntries[Size]; Data mEntries[Size];
unsigned mTrailingIdx; unsigned mTrailingIdx;
unsigned mMiddleIdx; unsigned mMiddleIdx;
unsigned mLeadingIdx; unsigned mLeadingIdx;
const double mRate; const double mRate;
wxLongLong mLastScrubTimeMillis; wxLongLong mLastScrubTimeMillis;
wxLongLong mLastTransformerTimeMillis { -1LL };
sampleCount mCredit { 0 };
sampleCount mDebt { 0 };
const long mMaxDebt;
mutable wxMutex mUpdating; mutable wxMutex mUpdating;
mutable wxCondition mAvailable { mUpdating }; mutable wxCondition mAvailable { mUpdating };
bool mNudged { false }; bool mNudged { false };
@ -1230,7 +1145,7 @@ AudioIO::AudioIO()
mLastPlaybackTimeMillis = 0; mLastPlaybackTimeMillis = 0;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
mScrubQueue = NULL; mScrubState = NULL;
mScrubDuration = 0; mScrubDuration = 0;
mSilentScrub = false; mSilentScrub = false;
#endif #endif
@ -2071,17 +1986,17 @@ int AudioIO::StartStream(const TransportTracks &tracks,
if (scrubbing) if (scrubbing)
{ {
const auto &scrubOptions = *options.pScrubbingOptions; const auto &scrubOptions = *options.pScrubbingOptions;
mScrubQueue = mScrubState =
std::make_unique<ScrubQueue>( std::make_unique<ScrubState>(
mPlaybackSchedule.mT0, mPlaybackSchedule.mT1, mPlaybackSchedule.mT0, mPlaybackSchedule.mT1,
scrubOptions.startClockTimeMillis, scrubOptions.startClockTimeMillis,
mRate, 2 * scrubOptions.minStutterTime, mRate,
scrubOptions); scrubOptions);
mScrubDuration = 0; mScrubDuration = 0;
mSilentScrub = false; mSilentScrub = false;
} }
else else
mScrubQueue.reset(); mScrubState.reset();
#endif #endif
// We signal the audio thread to call FillBuffers, to prime the RingBuffers // We signal the audio thread to call FillBuffers, to prime the RingBuffers
@ -2091,8 +2006,8 @@ int AudioIO::StartStream(const TransportTracks &tracks,
mAudioThreadShouldCallFillBuffersOnce = true; mAudioThreadShouldCallFillBuffersOnce = true;
while( mAudioThreadShouldCallFillBuffersOnce ) { while( mAudioThreadShouldCallFillBuffersOnce ) {
if (mScrubQueue) if (mScrubState)
mScrubQueue->Nudge(); mScrubState->Nudge();
wxMilliSleep( 50 ); wxMilliSleep( 50 );
} }
@ -2383,7 +2298,7 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers)
} }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
mScrubQueue.reset(); mScrubState.reset();
#endif #endif
} }
@ -2598,8 +2513,8 @@ void AudioIO::StopStream()
// //
mAudioThreadFillBuffersLoopRunning = false; mAudioThreadFillBuffersLoopRunning = false;
if (mScrubQueue) if (mScrubState)
mScrubQueue->Nudge(); mScrubState->Nudge();
// Audacity can deadlock if it tries to update meters while // Audacity can deadlock if it tries to update meters while
// we're stopping PortAudio (because the meter updating code // we're stopping PortAudio (because the meter updating code
@ -2707,8 +2622,8 @@ void AudioIO::StopStream()
{ {
// LLL: Experienced recursive yield here...once. // LLL: Experienced recursive yield here...once.
wxGetApp().Yield(true); // Pass true for onlyIfNeeded to avoid recursive call error. wxGetApp().Yield(true); // Pass true for onlyIfNeeded to avoid recursive call error.
if (mScrubQueue) if (mScrubState)
mScrubQueue->Nudge(); mScrubState->Nudge();
wxMilliSleep( 50 ); wxMilliSleep( 50 );
} }
@ -2809,7 +2724,7 @@ void AudioIO::StopStream()
#endif #endif
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
mScrubQueue.reset(); mScrubState.reset();
#endif #endif
if (mListener) { if (mListener) {
@ -2844,19 +2759,19 @@ bool AudioIO::IsPaused() const
} }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
bool AudioIO::EnqueueScrub bool AudioIO::UpdateScrub
(double endTimeOrSpeed, const ScrubbingOptions &options) (double endTimeOrSpeed, const ScrubbingOptions &options)
{ {
if (mScrubQueue) if (mScrubState)
return mScrubQueue->Producer(endTimeOrSpeed, options); return mScrubState->Update(endTimeOrSpeed, options);
else else
return false; return false;
} }
double AudioIO::GetLastTimeInScrubQueue() const double AudioIO::GetLastScrubTime() const
{ {
if (mScrubQueue) if (mScrubState)
return mScrubQueue->LastTimeInQueue(); return mScrubState->LastTrackTime();
else else
return -1.0; return -1.0;
} }
@ -3320,7 +3235,7 @@ AudioThread::ExitCode AudioThread::Entry()
gAudioIO->mAudioThreadFillBuffersLoopActive = false; gAudioIO->mAudioThreadFillBuffersLoopActive = false;
if (gAudioIO->mPlaybackSchedule.Interactive()) { if (gAudioIO->mPlaybackSchedule.Interactive()) {
// Rely on the Wait() in ScrubQueue::Consumer() // Rely on the Wait() in ScrubState::Consumer()
// This allows the scrubbing update interval to be made very short without // This allows the scrubbing update interval to be made very short without
// playback becoming intermittent. // playback becoming intermittent.
} }
@ -3923,7 +3838,7 @@ void AudioIO::FillBuffers()
// times, to ensure, that the buffer has a reasonable size // times, to ensure, that the buffer has a reasonable size
// This is the purpose of this loop. // This is the purpose of this loop.
// PRL: or, when scrubbing, we may get work repeatedly from the // PRL: or, when scrubbing, we may get work repeatedly from the
// scrub queue. // user interface.
bool done = false; bool done = false;
Maybe<wxMutexLocker> cleanup; Maybe<wxMutexLocker> cleanup;
do { do {
@ -4010,7 +3925,8 @@ void AudioIO::FillBuffers()
if (!done && mScrubDuration <= 0) if (!done && mScrubDuration <= 0)
{ {
sampleCount startSample, endSample; sampleCount startSample, endSample;
mScrubQueue->Consumer(startSample, endSample, mScrubDuration, cleanup); mScrubState->Get(
startSample, endSample, mScrubDuration, cleanup);
if (mScrubDuration < 0) if (mScrubDuration < 0)
{ {
// Can't play anything // Can't play anything

View File

@ -252,23 +252,18 @@ class AUDACITY_DLL_API AudioIO final {
void SeekStream(double seconds) { mSeek = seconds; } void SeekStream(double seconds) { mSeek = seconds; }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
bool IsScrubbing() const { return IsBusy() && mScrubQueue != 0; } bool IsScrubbing() const { return IsBusy() && mScrubState != 0; }
/** \brief enqueue a NEW scrub play interval, using the last end as the NEW start, /** \brief Notify scrubbing engine of desired position or speed.
* to be played over the same duration, as between this and the last * If options.adjustStart is true, then when mouse movement exceeds maximum
* enqueuing (or the starting of the stream). Except, we do not exceed maximum * scrub speed, adjust the beginning of the scrub interval rather than the
* scrub speed, so may need to adjust either the start or the end. * end, so that the scrub skips or "stutters" to stay near the cursor.
* If options.adjustStart is true, then when mouse movement exceeds maximum scrub speed,
* adjust the beginning of the scrub interval rather than the end, so that
* the scrub skips or "stutters" to stay near the cursor.
* Return true if some sound was really enqueued.
* But if the "stutter" is too short for the minimum, enqueue nothing and return false.
*/ */
bool EnqueueScrub(double endTimeOrSpeed, const ScrubbingOptions &options); bool UpdateScrub(double endTimeOrSpeed, const ScrubbingOptions &options);
/** \brief return the ending time of the last enqueued scrub interval. /** \brief return the ending time of the last scrub interval.
*/ */
double GetLastTimeInScrubQueue() const; double GetLastScrubTime() const;
#endif #endif
/** \brief Returns true if audio i/o is busy starting, stopping, playing, /** \brief Returns true if audio i/o is busy starting, stopping, playing,
@ -803,8 +798,8 @@ private:
wxMutex mSuspendAudioThread; wxMutex mSuspendAudioThread;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
struct ScrubQueue; struct ScrubState;
std::unique_ptr<ScrubQueue> mScrubQueue; std::unique_ptr<ScrubState> mScrubState;
bool mSilentScrub; bool mSilentScrub;
double mScrubSpeed; double mScrubSpeed;

View File

@ -476,7 +476,7 @@ bool Scrubber::StartSpeedPlay(double speed, double time0, double time1)
mOptions.minTime = 0; mOptions.minTime = 0;
mOptions.maxTime = time1; mOptions.maxTime = time1;
mOptions.minStutterTime = std::max(0.0, MinStutter); mOptions.minStutterTime = std::max(0.0, MinStutter);
mOptions.enqueueBySpeed = true; mOptions.bySpeed = true;
mOptions.adjustStart = false; mOptions.adjustStart = false;
mOptions.isPlayingAtSpeed = true; mOptions.isPlayingAtSpeed = true;
@ -529,12 +529,12 @@ void Scrubber::ContinueScrubbingPoll()
bool result = false; bool result = false;
if (mPaused) { if (mPaused) {
// When paused, enqueue silent scrubs. // When paused, make silent scrubs.
mOptions.minSpeed = 0.0; mOptions.minSpeed = 0.0;
mOptions.maxSpeed = mMaxSpeed; mOptions.maxSpeed = mMaxSpeed;
mOptions.adjustStart = false; mOptions.adjustStart = false;
mOptions.enqueueBySpeed = true; mOptions.bySpeed = true;
result = gAudioIO->EnqueueScrub(0, mOptions); result = gAudioIO->UpdateScrub(0, mOptions);
} }
else if (mSpeedPlaying) { else if (mSpeedPlaying) {
// default speed of 1.3 set, so that we can hear there is a problem // default speed of 1.3 set, so that we can hear there is a problem
@ -547,8 +547,8 @@ void Scrubber::ContinueScrubbingPoll()
mOptions.minSpeed = speed -0.01; mOptions.minSpeed = speed -0.01;
mOptions.maxSpeed = speed +0.01; mOptions.maxSpeed = speed +0.01;
mOptions.adjustStart = false; mOptions.adjustStart = false;
mOptions.enqueueBySpeed = true; mOptions.bySpeed = true;
result = gAudioIO->EnqueueScrub(speed, mOptions); result = gAudioIO->UpdateScrub(speed, mOptions);
} else { } else {
const wxMouseState state(::wxGetMouseState()); const wxMouseState state(::wxGetMouseState());
const auto trackPanel = mProject->GetTrackPanel(); const auto trackPanel = mProject->GetTrackPanel();
@ -556,14 +556,14 @@ void Scrubber::ContinueScrubbingPoll()
const auto &viewInfo = mProject->GetViewInfo(); const auto &viewInfo = mProject->GetViewInfo();
#ifdef DRAG_SCRUB #ifdef DRAG_SCRUB
if (mDragging && mSmoothScrollingScrub) { if (mDragging && mSmoothScrollingScrub) {
const auto lastTime = gAudioIO->GetLastTimeInScrubQueue(); const auto lastTime = gAudioIO->GetLastScrubTime();
const auto delta = mLastScrubPosition - position.x; const auto delta = mLastScrubPosition - position.x;
const double time = viewInfo.OffsetTimeByPixels(lastTime, delta); const double time = viewInfo.OffsetTimeByPixels(lastTime, delta);
mOptions.minSpeed = 0.0; mOptions.minSpeed = 0.0;
mOptions.maxSpeed = mMaxSpeed; mOptions.maxSpeed = mMaxSpeed;
mOptions.adjustStart = true; mOptions.adjustStart = true;
mOptions.enqueueBySpeed = false; mOptions.bySpeed = false;
result = gAudioIO->EnqueueScrub(time, mOptions); result = gAudioIO->UpdateScrub(time, mOptions);
mLastScrubPosition = position.x; mLastScrubPosition = position.x;
} }
else else
@ -576,12 +576,12 @@ void Scrubber::ContinueScrubbingPoll()
if (mSmoothScrollingScrub) { if (mSmoothScrollingScrub) {
const double speed = FindScrubSpeed(seek, time); const double speed = FindScrubSpeed(seek, time);
mOptions.enqueueBySpeed = true; mOptions.bySpeed = true;
result = gAudioIO->EnqueueScrub(speed, mOptions); result = gAudioIO->UpdateScrub(speed, mOptions);
} }
else { else {
mOptions.enqueueBySpeed = false; mOptions.bySpeed = false;
result = gAudioIO->EnqueueScrub(time, mOptions); result = gAudioIO->UpdateScrub(time, mOptions);
} }
} }
} }

View File

@ -44,7 +44,7 @@ struct ScrubbingOptions {
double maxTime {}; double maxTime {};
double minTime {}; double minTime {};
bool enqueueBySpeed {}; bool bySpeed {};
bool isPlayingAtSpeed{}; bool isPlayingAtSpeed{};
double delay {}; double delay {};