diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 57efe79e7..d392e9fad 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -522,25 +522,9 @@ 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, + ScrubState(double t0, double t1, wxLongLong startClockMillis, double rate, const ScrubbingOptions &options) : mTrailingIdx(0) @@ -574,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 @@ -594,7 +578,7 @@ struct AudioIO::ScrubQueue if (dd.duration <= 0) return false; - const sampleCount s1 ( options.enqueueBySpeed + const sampleCount s1 ( options.bySpeed ? s0.as_double() + lrint(dd.duration.as_double() * end) // end is a speed : lrint(end * mRate) // end is a time @@ -622,9 +606,9 @@ struct AudioIO::ScrubQueue } } - void Consumer(sampleCount &startSample, sampleCount &endSample, - sampleCount &duration, - Maybe &cleanup) + void Get(sampleCount &startSample, sampleCount &endSample, + sampleCount &duration, + Maybe &cleanup) { // Audio thread is ready for the next interval. @@ -641,7 +625,7 @@ struct AudioIO::ScrubQueue auto now = ::wxGetLocalTimeMillis(); if (mMiddleIdx != mLeadingIdx) { - Entry &entry = mEntries[mMiddleIdx]; + Data &entry = mEntries[mMiddleIdx]; if (entry.mDuration > 0) { // First use of the entry startSample = entry.mS0; @@ -667,20 +651,20 @@ struct AudioIO::ScrubQueue } } - double LastTimeInQueue() const + double LastTrackTime() const { // Needed by the main thread sometimes 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; } - ~ScrubQueue() {} + ~ScrubState() {} private: - struct Entry + struct Data { - Entry() + Data() : mS0(0) , mS1(0) , mGoal(0) @@ -688,7 +672,7 @@ private: , mSilence(0) {} - bool Init(Entry *previous, sampleCount s0, sampleCount s1, + bool Init(Data *previous, sampleCount s0, sampleCount s1, sampleCount duration, const ScrubbingOptions &options, double rate) { @@ -787,7 +771,7 @@ private: ) ); if (newDuration == 0) { - // Enqueue a silent scrub with s0 == s1 + // A silent scrub with s0 == s1 silent = true; s1 = s0; } @@ -818,22 +802,15 @@ private: return true; } - // 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; sampleCount mSilence; - - // The middleman Audio thread does not change these entries, but only - // changes indices in the queue structure. }; struct Duration { - Duration (ScrubQueue &queue_) : queue(queue_) {} + Duration (ScrubState &queue_) : queue(queue_) {} ~Duration () { if(!cancelled) @@ -842,7 +819,7 @@ private: void Cancel() { cancelled = true; } - ScrubQueue &queue; + ScrubState &queue; const wxLongLong clockTime { ::wxGetLocalTimeMillis() }; const sampleCount duration { static_cast (queue.mRate * (clockTime - queue.mLastScrubTimeMillis).ToDouble() / 1000.0) @@ -851,7 +828,7 @@ private: }; enum { Size = 10 }; - Entry mEntries[Size]; + Data mEntries[Size]; unsigned mTrailingIdx; unsigned mMiddleIdx; unsigned mLeadingIdx; @@ -1168,7 +1145,7 @@ AudioIO::AudioIO() mLastPlaybackTimeMillis = 0; #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - mScrubQueue = NULL; + mScrubState = NULL; mScrubDuration = 0; mSilentScrub = false; #endif @@ -2009,8 +1986,8 @@ int AudioIO::StartStream(const TransportTracks &tracks, if (scrubbing) { const auto &scrubOptions = *options.pScrubbingOptions; - mScrubQueue = - std::make_unique( + mScrubState = + std::make_unique( mPlaybackSchedule.mT0, mPlaybackSchedule.mT1, scrubOptions.startClockTimeMillis, mRate, @@ -2019,7 +1996,7 @@ int AudioIO::StartStream(const TransportTracks &tracks, mSilentScrub = false; } else - mScrubQueue.reset(); + mScrubState.reset(); #endif // We signal the audio thread to call FillBuffers, to prime the RingBuffers @@ -2029,8 +2006,8 @@ int AudioIO::StartStream(const TransportTracks &tracks, mAudioThreadShouldCallFillBuffersOnce = true; while( mAudioThreadShouldCallFillBuffersOnce ) { - if (mScrubQueue) - mScrubQueue->Nudge(); + if (mScrubState) + mScrubState->Nudge(); wxMilliSleep( 50 ); } @@ -2321,7 +2298,7 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers) } #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - mScrubQueue.reset(); + mScrubState.reset(); #endif } @@ -2536,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 @@ -2645,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 ); } @@ -2747,7 +2724,7 @@ void AudioIO::StopStream() #endif #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - mScrubQueue.reset(); + mScrubState.reset(); #endif if (mListener) { @@ -2782,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; } @@ -3258,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. } @@ -3861,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 cleanup; do { @@ -3948,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 diff --git a/src/AudioIO.h b/src/AudioIO.h index 46b96b446..33145598e 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -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 mScrubQueue; + struct ScrubState; + std::unique_ptr mScrubState; bool mSilentScrub; double mScrubSpeed; diff --git a/src/tracks/ui/Scrubbing.cpp b/src/tracks/ui/Scrubbing.cpp index 187103a11..7ac90e0ff 100644 --- a/src/tracks/ui/Scrubbing.cpp +++ b/src/tracks/ui/Scrubbing.cpp @@ -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); } } } diff --git a/src/tracks/ui/Scrubbing.h b/src/tracks/ui/Scrubbing.h index 139494097..63268118e 100644 --- a/src/tracks/ui/Scrubbing.h +++ b/src/tracks/ui/Scrubbing.h @@ -44,7 +44,7 @@ struct ScrubbingOptions { double maxTime {}; double minTime {}; - bool enqueueBySpeed {}; + bool bySpeed {}; bool isPlayingAtSpeed{}; double delay {};