diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index c81647989..c78cc0fb8 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -1934,6 +1934,7 @@ int AudioIO::StartStream(const TransportTracks &tracks, mT0 = t0 - preRoll; mT1 = t1; mRecordingSchedule = {}; + mRecordingPosition.store(0.0, std::memory_order_relaxed); mRecordingSchedule.mPreRoll = preRoll; mRecordingSchedule.mLatencyCorrection = (gPrefs->ReadDouble(wxT("/AudioIO/LatencyCorrection"), @@ -2564,6 +2565,7 @@ void AudioIO::StopStream() auto cleanup = finally ( [this] { ClearRecordingException(); mRecordingSchedule = {}; // free arrays + mRecordingPosition.store(0.0, std::memory_order_relaxed); } ); if( mPortStreamV19 == NULL @@ -3989,8 +3991,9 @@ void AudioIO::FillBuffers() GuardedCall( [&] { // start record buffering const auto avail = GetCommonlyAvailCapture(); // samples + const auto position = mRecordingPosition.load(std::memory_order_relaxed); const auto remainingTime = - std::max(0.0, mRecordingSchedule.ToConsume()); + std::max(0.0, mRecordingSchedule.ToConsume(position)); // This may be a very big double number: const auto remainingSamples = remainingTime * mRate; bool latencyCorrected = true; @@ -4028,7 +4031,7 @@ void AudioIO::FillBuffers() // Leftward shift // discard some samples from the ring buffers. size_t size = floor( - mRecordingSchedule.ToDiscard() * mRate ); + mRecordingSchedule.ToDiscard(position) * mRate ); // The ring buffer might have grown concurrently -- don't discard more // than the "avail" value noted above. @@ -4051,7 +4054,8 @@ void AudioIO::FillBuffers() totalCrossfadeLength = data.size(); if (totalCrossfadeLength) { crossfadeStart = - floor(mRecordingSchedule.Consumed() * mCaptureTracks[i]->GetRate()); + floor(mRecordingSchedule.Consumed(position) * + mCaptureTracks[i]->GetRate()); if (crossfadeStart < totalCrossfadeLength) pCrossfadeSrc = data.data() + crossfadeStart; } @@ -4139,8 +4143,13 @@ void AudioIO::FillBuffers() } } // end loop over capture channels - // Now update the recording shedule position - mRecordingSchedule.mPosition += avail / mRate; + // Now update the recording schedule position + // Main thread may need to read it for drawing, so update atomically + // Relaxed order here, with the frequent memory fencing done by + // the ring buffers, should give frequent enough update in the + // main thread. + mRecordingPosition.store( + position + avail / mRate, std::memory_order_relaxed); mRecordingSchedule.mLatencyCorrected = latencyCorrected; if (mListener && !blockFileLog.IsEmpty()) @@ -5463,17 +5472,29 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, return callbackReturn; } -double AudioIO::RecordingSchedule::ToConsume() const +double AudioIO::RecordingSchedule::ToConsume(double position) const { - return mDuration - Consumed(); + return mDuration - Consumed(position); } -double AudioIO::RecordingSchedule::Consumed() const +double AudioIO::RecordingSchedule::Consumed(double position) const { - return std::max( 0.0, mPosition + TotalCorrection() ); + return std::max( 0.0, position + TotalCorrection() ); } -double AudioIO::RecordingSchedule::ToDiscard() const +double AudioIO::RecordingSchedule::ToDiscard(double position) const { - return std::max(0.0, -( mPosition + TotalCorrection() ) ); + return std::max(0.0, -( position + TotalCorrection() ) ); +} + +bool AudioIO::IsCapturing() const +{ + // This is for purposes of choosing the color of the play indicator + // Returns false during a pre-roll but true when time before actual recording + // is less than latency + return + GetNumCaptureChannels() > 0 && + mRecordingSchedule.ToDiscard( + mRecordingPosition.load(std::memory_order_relaxed)) <= + -std::min(0.0, mRecordingSchedule.mLatencyCorrection); } diff --git a/src/AudioIO.h b/src/AudioIO.h index c9ee5578d..047962329 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -18,6 +18,7 @@ #include "Experimental.h" #include "MemoryX.h" +#include #include #include #include @@ -398,6 +399,9 @@ class AUDACITY_DLL_API AudioIO final { unsigned GetNumPlaybackChannels() const { return mNumPlaybackChannels; } unsigned GetNumCaptureChannels() const { return mNumCaptureChannels; } + // Meaning really capturing, not just pre-rolling + bool IsCapturing() const; + /** \brief Array of common audio sample rates * * These are the rates we will always support, regardless of hardware support @@ -846,16 +850,20 @@ private: double mDuration{}; PRCrossfadeData mCrossfadeData; - // These are initialized by the main thread, then updated + // This is initialized by the main thread, then updated // only by the thread calling FillBuffers: - double mPosition{}; bool mLatencyCorrected{}; double TotalCorrection() const { return mLatencyCorrection - mPreRoll; } - double ToConsume() const; - double Consumed() const; - double ToDiscard() const; + double ToConsume(double position) const; + double Consumed(double position) const; + double ToDiscard(double position) const; } mRecordingSchedule{}; + + // This is initialized by the main thread, then updated + // only by the thread calling FillBuffers, but may need to be read by the + // main thread to draw display correctly. + std::atomic mRecordingPosition{}; }; #endif diff --git a/src/tracks/ui/PlayIndicatorOverlay.cpp b/src/tracks/ui/PlayIndicatorOverlay.cpp index 0305a5580..ba81c1945 100644 --- a/src/tracks/ui/PlayIndicatorOverlay.cpp +++ b/src/tracks/ui/PlayIndicatorOverlay.cpp @@ -60,7 +60,7 @@ std::pair PlayIndicatorOverlayBase::DoGetRectangle(wxSize size) void PlayIndicatorOverlayBase::Draw(OverlayPanel &panel, wxDC &dc) { // Set play/record color - bool rec = (gAudioIO->GetNumCaptureChannels() > 0); + bool rec = gAudioIO->IsCapturing(); AColor::IndicatorColor(&dc, !rec); mLastIndicatorX = mNewIndicatorX; if (!between_incexc(0, mLastIndicatorX, dc.GetSize().GetWidth()))