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:
commit
bdf1cb32fe
230
src/AudioIO.cpp
230
src/AudioIO.cpp
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user