1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-02 00:49:33 +02:00

Changes of names and comments relating to scrub, don't mention queue

This commit is contained in:
Paul Licameli 2018-08-15 20:36:09 -04:00
parent 4a98ba03dd
commit 550d514e06
4 changed files with 64 additions and 91 deletions

View File

@ -522,25 +522,9 @@ 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 rate,
const ScrubbingOptions &options) const ScrubbingOptions &options)
: mTrailingIdx(0) : mTrailingIdx(0)
@ -574,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
@ -594,7 +578,7 @@ struct AudioIO::ScrubQueue
if (dd.duration <= 0) if (dd.duration <= 0)
return false; return false;
const sampleCount s1 ( options.enqueueBySpeed const sampleCount s1 ( options.bySpeed
? s0.as_double() + ? s0.as_double() +
lrint(dd.duration.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
@ -622,9 +606,9 @@ 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)
{ {
// Audio thread is ready for the next interval. // Audio thread is ready for the next interval.
@ -641,7 +625,7 @@ struct AudioIO::ScrubQueue
auto now = ::wxGetLocalTimeMillis(); auto now = ::wxGetLocalTimeMillis();
if (mMiddleIdx != mLeadingIdx) { if (mMiddleIdx != mLeadingIdx) {
Entry &entry = mEntries[mMiddleIdx]; Data &entry = mEntries[mMiddleIdx];
if (entry.mDuration > 0) { if (entry.mDuration > 0) {
// First use of the entry // First use of the entry
startSample = entry.mS0; startSample = entry.mS0;
@ -667,20 +651,20 @@ struct AudioIO::ScrubQueue
} }
} }
double LastTimeInQueue() const double LastTrackTime() const
{ {
// Needed by the main thread sometimes // Needed by the main thread sometimes
wxMutexLocker locker(mUpdating); wxMutexLocker locker(mUpdating);
const Entry &previous = mEntries[(mLeadingIdx + Size - 1) % Size]; const Data &previous = mEntries[(mLeadingIdx + Size - 1) % Size];
return previous.mS1.as_double() / mRate; return previous.mS1.as_double() / mRate;
} }
~ScrubQueue() {} ~ScrubState() {}
private: private:
struct Entry struct Data
{ {
Entry() Data()
: mS0(0) : mS0(0)
, mS1(0) , mS1(0)
, mGoal(0) , mGoal(0)
@ -688,7 +672,7 @@ private:
, mSilence(0) , mSilence(0)
{} {}
bool Init(Entry *previous, sampleCount s0, sampleCount s1, bool Init(Data *previous, sampleCount s0, sampleCount s1,
sampleCount duration, sampleCount duration,
const ScrubbingOptions &options, double rate) const ScrubbingOptions &options, double rate)
{ {
@ -787,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;
} }
@ -818,22 +802,15 @@ private:
return true; return true;
} }
// 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; 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)
@ -842,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)
@ -851,7 +828,7 @@ 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;
@ -1168,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
@ -2009,8 +1986,8 @@ 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, mRate,
@ -2019,7 +1996,7 @@ int AudioIO::StartStream(const TransportTracks &tracks,
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
@ -2029,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 );
} }
@ -2321,7 +2298,7 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers)
} }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
mScrubQueue.reset(); mScrubState.reset();
#endif #endif
} }
@ -2536,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
@ -2645,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 );
} }
@ -2747,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) {
@ -2782,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;
} }
@ -3258,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.
} }
@ -3861,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 {
@ -3948,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 {};