mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-19 17:40:15 +02:00
Some simplifications and renamings related to ScrubState
This commit is contained in:
commit
bdf1cb32fe
244
src/AudioIO.cpp
244
src/AudioIO.cpp
@ -522,33 +522,16 @@ constexpr size_t TimeQueueGrainSize = 2000;
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
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
|
||||
struct AudioIO::ScrubState
|
||||
{
|
||||
ScrubQueue(double t0, double t1, wxLongLong startClockMillis,
|
||||
double rate, double maxDebt,
|
||||
ScrubState(double t0, double t1, wxLongLong startClockMillis,
|
||||
double rate,
|
||||
const ScrubbingOptions &options)
|
||||
: mTrailingIdx(0)
|
||||
, mMiddleIdx(1)
|
||||
, mLeadingIdx(1)
|
||||
, mRate(rate)
|
||||
, mLastScrubTimeMillis(startClockMillis)
|
||||
, mMaxDebt { lrint(maxDebt * rate) }
|
||||
, mUpdating()
|
||||
{
|
||||
const sampleCount s0 { llrint( mRate *
|
||||
@ -565,15 +548,6 @@ struct AudioIO::ScrubQueue
|
||||
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:
|
||||
// Audio stream needs to be unblocked
|
||||
@ -584,7 +558,7 @@ struct AudioIO::ScrubQueue
|
||||
mAvailable.Signal();
|
||||
}
|
||||
|
||||
bool Producer(double end, const ScrubbingOptions &options)
|
||||
bool Update(double end, const ScrubbingOptions &options)
|
||||
{
|
||||
// Main thread indicates a scrubbing interval
|
||||
|
||||
@ -601,18 +575,16 @@ struct AudioIO::ScrubQueue
|
||||
// Use the previous end as NEW start.
|
||||
const auto s0 = previous->mS1;
|
||||
Duration dd { *this };
|
||||
const auto &origDuration = dd.duration;
|
||||
if (origDuration <= 0)
|
||||
if (dd.duration <= 0)
|
||||
return false;
|
||||
|
||||
auto actualDuration = origDuration;
|
||||
const sampleCount s1 ( options.enqueueBySpeed
|
||||
const sampleCount s1 ( options.bySpeed
|
||||
? 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
|
||||
);
|
||||
auto success =
|
||||
current->Init(previous, s0, s1, actualDuration, options, mRate);
|
||||
current->Init(previous, s0, s1, dd.duration, options, mRate);
|
||||
if (success)
|
||||
mLeadingIdx = next;
|
||||
else {
|
||||
@ -620,21 +592,6 @@ struct AudioIO::ScrubQueue
|
||||
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();
|
||||
return result;
|
||||
}
|
||||
@ -649,20 +606,16 @@ struct AudioIO::ScrubQueue
|
||||
}
|
||||
}
|
||||
|
||||
void Consumer(sampleCount &startSample, sampleCount &endSample,
|
||||
sampleCount &duration,
|
||||
Maybe<wxMutexLocker> &cleanup)
|
||||
void Get(sampleCount &startSample, sampleCount &endSample,
|
||||
sampleCount &duration,
|
||||
Maybe<wxMutexLocker> &cleanup)
|
||||
{
|
||||
// Audio thread is ready for the next interval.
|
||||
|
||||
// MAY ADVANCE mMiddleIdx, WHICH MAY EQUAL mLeadingIdx, BUT DOES NOT PASS IT.
|
||||
|
||||
bool checkDebt = false;
|
||||
if (!cleanup) {
|
||||
cleanup.create(mUpdating);
|
||||
|
||||
// Check for cancellation of work only when re-enetering the cricial section
|
||||
checkDebt = true;
|
||||
}
|
||||
while(!mNudged && mMiddleIdx == mLeadingIdx)
|
||||
mAvailable.Wait();
|
||||
@ -671,84 +624,61 @@ struct AudioIO::ScrubQueue
|
||||
|
||||
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) {
|
||||
// There is still work in the queue, after cancelling debt
|
||||
Entry &entry = mEntries[mMiddleIdx];
|
||||
startSample = entry.mS0;
|
||||
endSample = entry.mS1;
|
||||
duration = entry.mDuration;
|
||||
mTrailingIdx = mMiddleIdx;
|
||||
mMiddleIdx = (mMiddleIdx + 1) % Size;
|
||||
mCredit += duration;
|
||||
Data &entry = mEntries[mMiddleIdx];
|
||||
if (entry.mDuration > 0) {
|
||||
// First use of the entry
|
||||
startSample = entry.mS0;
|
||||
endSample = entry.mS1;
|
||||
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;
|
||||
mMiddleIdx = (mMiddleIdx + 1) % Size;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We got the shut-down signal, or we got nudged, or we discarded all the work.
|
||||
startSample = endSample = duration = -1L;
|
||||
}
|
||||
|
||||
if (checkDebt)
|
||||
mLastTransformerTimeMillis = now;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
double LastTrackTime() const
|
||||
{
|
||||
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)
|
||||
, mS1(0)
|
||||
, mGoal(0)
|
||||
, mDuration(0)
|
||||
, mSilence(0)
|
||||
{}
|
||||
|
||||
bool Init(Entry *previous, sampleCount s0, sampleCount s1,
|
||||
sampleCount &duration /* in/out */,
|
||||
bool Init(Data *previous, sampleCount s0, sampleCount s1,
|
||||
sampleCount duration,
|
||||
const ScrubbingOptions &options, double rate)
|
||||
{
|
||||
auto origDuration = duration;
|
||||
mSilence = 0;
|
||||
|
||||
const bool &adjustStart = options.adjustStart;
|
||||
|
||||
wxASSERT(duration > 0);
|
||||
@ -841,7 +771,7 @@ private:
|
||||
)
|
||||
);
|
||||
if (newDuration == 0) {
|
||||
// Enqueue a silent scrub with s0 == s1
|
||||
// A silent scrub with s0 == s1
|
||||
silent = true;
|
||||
s1 = s0;
|
||||
}
|
||||
@ -866,31 +796,21 @@ private:
|
||||
mS0 = s0;
|
||||
mS1 = s1;
|
||||
mDuration = duration;
|
||||
if (duration < origDuration)
|
||||
mSilence = origDuration - duration;
|
||||
|
||||
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 mS1;
|
||||
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;
|
||||
|
||||
// The middleman Audio thread does not change these entries, but only
|
||||
// changes indices in the queue structure.
|
||||
sampleCount mSilence;
|
||||
};
|
||||
|
||||
struct Duration {
|
||||
Duration (ScrubQueue &queue_) : queue(queue_) {}
|
||||
Duration (ScrubState &queue_) : queue(queue_) {}
|
||||
~Duration ()
|
||||
{
|
||||
if(!cancelled)
|
||||
@ -899,7 +819,7 @@ private:
|
||||
|
||||
void Cancel() { cancelled = true; }
|
||||
|
||||
ScrubQueue &queue;
|
||||
ScrubState &queue;
|
||||
const wxLongLong clockTime { ::wxGetLocalTimeMillis() };
|
||||
const sampleCount duration { static_cast<long long>
|
||||
(queue.mRate * (clockTime - queue.mLastScrubTimeMillis).ToDouble() / 1000.0)
|
||||
@ -908,18 +828,13 @@ private:
|
||||
};
|
||||
|
||||
enum { Size = 10 };
|
||||
Entry mEntries[Size];
|
||||
Data mEntries[Size];
|
||||
unsigned mTrailingIdx;
|
||||
unsigned mMiddleIdx;
|
||||
unsigned mLeadingIdx;
|
||||
const double mRate;
|
||||
wxLongLong mLastScrubTimeMillis;
|
||||
|
||||
wxLongLong mLastTransformerTimeMillis { -1LL };
|
||||
sampleCount mCredit { 0 };
|
||||
sampleCount mDebt { 0 };
|
||||
const long mMaxDebt;
|
||||
|
||||
mutable wxMutex mUpdating;
|
||||
mutable wxCondition mAvailable { mUpdating };
|
||||
bool mNudged { false };
|
||||
@ -1230,7 +1145,7 @@ AudioIO::AudioIO()
|
||||
mLastPlaybackTimeMillis = 0;
|
||||
|
||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||
mScrubQueue = NULL;
|
||||
mScrubState = NULL;
|
||||
mScrubDuration = 0;
|
||||
mSilentScrub = false;
|
||||
#endif
|
||||
@ -2071,17 +1986,17 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
||||
if (scrubbing)
|
||||
{
|
||||
const auto &scrubOptions = *options.pScrubbingOptions;
|
||||
mScrubQueue =
|
||||
std::make_unique<ScrubQueue>(
|
||||
mScrubState =
|
||||
std::make_unique<ScrubState>(
|
||||
mPlaybackSchedule.mT0, mPlaybackSchedule.mT1,
|
||||
scrubOptions.startClockTimeMillis,
|
||||
mRate, 2 * scrubOptions.minStutterTime,
|
||||
mRate,
|
||||
scrubOptions);
|
||||
mScrubDuration = 0;
|
||||
mSilentScrub = false;
|
||||
}
|
||||
else
|
||||
mScrubQueue.reset();
|
||||
mScrubState.reset();
|
||||
#endif
|
||||
|
||||
// We signal the audio thread to call FillBuffers, to prime the RingBuffers
|
||||
@ -2091,8 +2006,8 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
||||
mAudioThreadShouldCallFillBuffersOnce = true;
|
||||
|
||||
while( mAudioThreadShouldCallFillBuffersOnce ) {
|
||||
if (mScrubQueue)
|
||||
mScrubQueue->Nudge();
|
||||
if (mScrubState)
|
||||
mScrubState->Nudge();
|
||||
wxMilliSleep( 50 );
|
||||
}
|
||||
|
||||
@ -2383,7 +2298,7 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers)
|
||||
}
|
||||
|
||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||
mScrubQueue.reset();
|
||||
mScrubState.reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -2598,8 +2513,8 @@ void AudioIO::StopStream()
|
||||
//
|
||||
|
||||
mAudioThreadFillBuffersLoopRunning = false;
|
||||
if (mScrubQueue)
|
||||
mScrubQueue->Nudge();
|
||||
if (mScrubState)
|
||||
mScrubState->Nudge();
|
||||
|
||||
// Audacity can deadlock if it tries to update meters while
|
||||
// we're stopping PortAudio (because the meter updating code
|
||||
@ -2707,8 +2622,8 @@ void AudioIO::StopStream()
|
||||
{
|
||||
// LLL: Experienced recursive yield here...once.
|
||||
wxGetApp().Yield(true); // Pass true for onlyIfNeeded to avoid recursive call error.
|
||||
if (mScrubQueue)
|
||||
mScrubQueue->Nudge();
|
||||
if (mScrubState)
|
||||
mScrubState->Nudge();
|
||||
wxMilliSleep( 50 );
|
||||
}
|
||||
|
||||
@ -2809,7 +2724,7 @@ void AudioIO::StopStream()
|
||||
#endif
|
||||
|
||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||
mScrubQueue.reset();
|
||||
mScrubState.reset();
|
||||
#endif
|
||||
|
||||
if (mListener) {
|
||||
@ -2844,19 +2759,19 @@ bool AudioIO::IsPaused() const
|
||||
}
|
||||
|
||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||
bool AudioIO::EnqueueScrub
|
||||
bool AudioIO::UpdateScrub
|
||||
(double endTimeOrSpeed, const ScrubbingOptions &options)
|
||||
{
|
||||
if (mScrubQueue)
|
||||
return mScrubQueue->Producer(endTimeOrSpeed, options);
|
||||
if (mScrubState)
|
||||
return mScrubState->Update(endTimeOrSpeed, options);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
double AudioIO::GetLastTimeInScrubQueue() const
|
||||
double AudioIO::GetLastScrubTime() const
|
||||
{
|
||||
if (mScrubQueue)
|
||||
return mScrubQueue->LastTimeInQueue();
|
||||
if (mScrubState)
|
||||
return mScrubState->LastTrackTime();
|
||||
else
|
||||
return -1.0;
|
||||
}
|
||||
@ -3320,7 +3235,7 @@ AudioThread::ExitCode AudioThread::Entry()
|
||||
gAudioIO->mAudioThreadFillBuffersLoopActive = false;
|
||||
|
||||
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
|
||||
// playback becoming intermittent.
|
||||
}
|
||||
@ -3923,7 +3838,7 @@ void AudioIO::FillBuffers()
|
||||
// times, to ensure, that the buffer has a reasonable size
|
||||
// This is the purpose of this loop.
|
||||
// PRL: or, when scrubbing, we may get work repeatedly from the
|
||||
// scrub queue.
|
||||
// user interface.
|
||||
bool done = false;
|
||||
Maybe<wxMutexLocker> cleanup;
|
||||
do {
|
||||
@ -4010,7 +3925,8 @@ void AudioIO::FillBuffers()
|
||||
if (!done && mScrubDuration <= 0)
|
||||
{
|
||||
sampleCount startSample, endSample;
|
||||
mScrubQueue->Consumer(startSample, endSample, mScrubDuration, cleanup);
|
||||
mScrubState->Get(
|
||||
startSample, endSample, mScrubDuration, cleanup);
|
||||
if (mScrubDuration < 0)
|
||||
{
|
||||
// Can't play anything
|
||||
|
@ -252,23 +252,18 @@ class AUDACITY_DLL_API AudioIO final {
|
||||
void SeekStream(double seconds) { mSeek = seconds; }
|
||||
|
||||
#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,
|
||||
* to be played over the same duration, as between this and the last
|
||||
* enqueuing (or the starting of the stream). Except, we do not exceed maximum
|
||||
* scrub speed, so may need to adjust either the start or the end.
|
||||
* 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.
|
||||
/** \brief Notify scrubbing engine of desired position or speed.
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
|
||||
/** \brief Returns true if audio i/o is busy starting, stopping, playing,
|
||||
@ -803,8 +798,8 @@ private:
|
||||
wxMutex mSuspendAudioThread;
|
||||
|
||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||
struct ScrubQueue;
|
||||
std::unique_ptr<ScrubQueue> mScrubQueue;
|
||||
struct ScrubState;
|
||||
std::unique_ptr<ScrubState> mScrubState;
|
||||
|
||||
bool mSilentScrub;
|
||||
double mScrubSpeed;
|
||||
|
@ -476,7 +476,7 @@ bool Scrubber::StartSpeedPlay(double speed, double time0, double time1)
|
||||
mOptions.minTime = 0;
|
||||
mOptions.maxTime = time1;
|
||||
mOptions.minStutterTime = std::max(0.0, MinStutter);
|
||||
mOptions.enqueueBySpeed = true;
|
||||
mOptions.bySpeed = true;
|
||||
mOptions.adjustStart = false;
|
||||
mOptions.isPlayingAtSpeed = true;
|
||||
|
||||
@ -529,12 +529,12 @@ void Scrubber::ContinueScrubbingPoll()
|
||||
|
||||
bool result = false;
|
||||
if (mPaused) {
|
||||
// When paused, enqueue silent scrubs.
|
||||
// When paused, make silent scrubs.
|
||||
mOptions.minSpeed = 0.0;
|
||||
mOptions.maxSpeed = mMaxSpeed;
|
||||
mOptions.adjustStart = false;
|
||||
mOptions.enqueueBySpeed = true;
|
||||
result = gAudioIO->EnqueueScrub(0, mOptions);
|
||||
mOptions.bySpeed = true;
|
||||
result = gAudioIO->UpdateScrub(0, mOptions);
|
||||
}
|
||||
else if (mSpeedPlaying) {
|
||||
// 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.maxSpeed = speed +0.01;
|
||||
mOptions.adjustStart = false;
|
||||
mOptions.enqueueBySpeed = true;
|
||||
result = gAudioIO->EnqueueScrub(speed, mOptions);
|
||||
mOptions.bySpeed = true;
|
||||
result = gAudioIO->UpdateScrub(speed, mOptions);
|
||||
} else {
|
||||
const wxMouseState state(::wxGetMouseState());
|
||||
const auto trackPanel = mProject->GetTrackPanel();
|
||||
@ -556,14 +556,14 @@ void Scrubber::ContinueScrubbingPoll()
|
||||
const auto &viewInfo = mProject->GetViewInfo();
|
||||
#ifdef DRAG_SCRUB
|
||||
if (mDragging && mSmoothScrollingScrub) {
|
||||
const auto lastTime = gAudioIO->GetLastTimeInScrubQueue();
|
||||
const auto lastTime = gAudioIO->GetLastScrubTime();
|
||||
const auto delta = mLastScrubPosition - position.x;
|
||||
const double time = viewInfo.OffsetTimeByPixels(lastTime, delta);
|
||||
mOptions.minSpeed = 0.0;
|
||||
mOptions.maxSpeed = mMaxSpeed;
|
||||
mOptions.adjustStart = true;
|
||||
mOptions.enqueueBySpeed = false;
|
||||
result = gAudioIO->EnqueueScrub(time, mOptions);
|
||||
mOptions.bySpeed = false;
|
||||
result = gAudioIO->UpdateScrub(time, mOptions);
|
||||
mLastScrubPosition = position.x;
|
||||
}
|
||||
else
|
||||
@ -576,12 +576,12 @@ void Scrubber::ContinueScrubbingPoll()
|
||||
|
||||
if (mSmoothScrollingScrub) {
|
||||
const double speed = FindScrubSpeed(seek, time);
|
||||
mOptions.enqueueBySpeed = true;
|
||||
result = gAudioIO->EnqueueScrub(speed, mOptions);
|
||||
mOptions.bySpeed = true;
|
||||
result = gAudioIO->UpdateScrub(speed, mOptions);
|
||||
}
|
||||
else {
|
||||
mOptions.enqueueBySpeed = false;
|
||||
result = gAudioIO->EnqueueScrub(time, mOptions);
|
||||
mOptions.bySpeed = false;
|
||||
result = gAudioIO->UpdateScrub(time, mOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ struct ScrubbingOptions {
|
||||
double maxTime {};
|
||||
double minTime {};
|
||||
|
||||
bool enqueueBySpeed {};
|
||||
bool bySpeed {};
|
||||
bool isPlayingAtSpeed{};
|
||||
|
||||
double delay {};
|
||||
|
Loading…
x
Reference in New Issue
Block a user