From f036700b09825008abceac81b14bad4435d2db27 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Fri, 10 Aug 2018 13:18:13 -0400 Subject: [PATCH] Make PlaybackSchedule::mTime atomic and rename some functions... ... The variable ought to be atomic because it is read and written by different threads. Use local variables to avoid repeated reads of the atomic. --- src/AudioIO.cpp | 107 ++++++++++++++++++++++++++++++------------------ src/AudioIO.h | 36 +++++++++++++--- 2 files changed, 98 insertions(+), 45 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index e92ea2a6d..ef1b20227 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -2206,14 +2206,16 @@ int AudioIO::StartStream(const TransportTracks &tracks, if (options.pStartTime) { // Calculate the NEW time position - mPlaybackSchedule.mTime = - std::max(mPlaybackSchedule.mT0, - std::min(mPlaybackSchedule.mT1, *options.pStartTime)); + const auto time = mPlaybackSchedule.ClampTrackTime( *options.pStartTime ); + + // Main thread's initialization of mTime + mPlaybackSchedule.SetTrackTime( time ); + // Reset mixer positions for all playback tracks unsigned numMixers = mPlaybackTracks.size(); for (unsigned ii = 0; ii < numMixers; ++ii) - mPlaybackMixers[ii]->Reposition(mPlaybackSchedule.mTime); - mPlaybackSchedule.RealTimeInit(); + mPlaybackMixers[ii]->Reposition( time ); + mPlaybackSchedule.RealTimeInit( time ); } #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT @@ -2875,7 +2877,8 @@ void AudioIO::PlaybackSchedule::Init( // desired length of recording mT1 -= pRecordingSchedule->mLatencyCorrection; - mTime = mT0; + // Main thread's initialization of mTime + SetTrackTime( mT0 ); mPlayMode = options.playLooped ? PlaybackSchedule::PLAY_LOOPED @@ -2913,17 +2916,25 @@ void AudioIO::PlaybackSchedule::Init( mWarpedLength = RealDuration(mT1); } -double AudioIO::PlaybackSchedule::LimitStreamTime() const +double AudioIO::PlaybackSchedule::LimitTrackTime() const { + // Track time readout for the main thread // Allows for forward or backward play - if (ReversedTime()) - return std::max(mT1, std::min(mT0, mTime)); - else - return std::max(mT0, std::min(mT1, mTime)); + return ClampTrackTime( GetTrackTime() ); } -double AudioIO::PlaybackSchedule::NormalizeStreamTime() const +double AudioIO::PlaybackSchedule::ClampTrackTime( double trackTime ) const { + if (ReversedTime()) + return std::max(mT1, std::min(mT0, trackTime)); + else + return std::max(mT0, std::min(mT1, trackTime)); +} + +double AudioIO::PlaybackSchedule::NormalizeTrackTime() const +{ + // Track time readout for the main thread + // dmazzoni: This function is needed for two reasons: // One is for looped-play mode - this function makes sure that the // position indicator keeps wrapping around. The other reason is @@ -2936,14 +2947,16 @@ double AudioIO::PlaybackSchedule::NormalizeStreamTime() const // mode. In this case, we should jump over a defined "gap" in the // audio. - auto absoluteTime = mTime; + double absoluteTime; #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT // Limit the time between t0 and t1 if not scrubbing. // Should the limiting be necessary in any play mode if there are no bugs? - if (!Interactive()) + if (Interactive()) + absoluteTime = GetTrackTime(); + else #endif - absoluteTime = LimitStreamTime(); + absoluteTime = LimitTrackTime(); if (mCutPreviewGapLen > 0) { @@ -2958,10 +2971,12 @@ double AudioIO::PlaybackSchedule::NormalizeStreamTime() const double AudioIO::GetStreamTime() { + // Track time readout for the main thread + if( !IsStreamActive() ) return BAD_STREAM_TIME; - return mPlaybackSchedule.NormalizeStreamTime(); + return mPlaybackSchedule.NormalizeTrackTime(); } @@ -4589,7 +4604,7 @@ void AudioIO::AILAProcess(double maxPeak) { mAILAMax = max(mAILAMax, maxPeak); - if ((mAILATotalAnalysis == 0 || mAILAAnalysisCounter < mAILATotalAnalysis) && mPlaybackSchedule.mTime - mAILALastStartTime >= mAILAAnalysisTime) { + if ((mAILATotalAnalysis == 0 || mAILAAnalysisCounter < mAILATotalAnalysis) && mPlaybackSchedule.GetTrackTime() - mAILALastStartTime >= mAILAAnalysisTime) { auto ToLinearIfDB = [](double value, int dbRange) { if (dbRange >= 0) value = pow(10.0, (-(1.0-value) * dbRange)/20.0); @@ -4670,7 +4685,7 @@ void AudioIO::AILAProcess(double maxPeak) { mAILAMax = 0; wxPrintf("\tA decision was made @ %f\n", mAILAAnalysisEndTime); mAILAClipped = false; - mAILALastStartTime = mPlaybackSchedule.mTime; + mAILALastStartTime = mPlaybackSchedule.GetTrackTime(); if (changetype == 0) mAILAChangeFactor *= 0.8; //time factor @@ -5138,7 +5153,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer, // "Consume" only as much as the ring buffers produced, which may // be less than framesPerBuffer (during "stutter") if (mPlaybackSchedule.Interactive()) - mPlaybackSchedule.mTime = mScrubQueue->Consumer( maxLen ); + mPlaybackSchedule.SetTrackTime( mScrubQueue->Consumer( maxLen ) ); #endif em.RealtimeProcessEnd(); @@ -5212,7 +5227,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer, len < framesPerBuffer) ) { // Assume that any good partial buffer should be written leftmost // and zeroes will be padded after; label the zeroes. - auto start = mPlaybackSchedule.mTime + + auto start = mPlaybackSchedule.GetTrackTime() + len / mRate + mRecordingSchedule.mLatencyCorrection; auto duration = (framesPerBuffer - len) / mRate; auto interval = std::make_pair( start, duration ); @@ -5384,17 +5399,18 @@ PaStreamCallbackResult AudioIO::CallbackDoSeek() wxMilliSleep( 50 ); } - // Calculate the NEW time position - mPlaybackSchedule.mTime += mSeek; - mPlaybackSchedule.mTime = mPlaybackSchedule.LimitStreamTime(); + // Calculate the NEW time position, in the PortAudio callback + const auto time = mPlaybackSchedule.ClampTrackTime( + mPlaybackSchedule.GetTrackTime() + mSeek ); + mPlaybackSchedule.SetTrackTime( time ); mSeek = 0.0; - - mPlaybackSchedule.RealTimeInit(); + + mPlaybackSchedule.RealTimeInit( time ); // Reset mixer positions and flush buffers for all tracks for (size_t i = 0; i < numPlaybackTracks; i++) { - mPlaybackMixers[i]->Reposition( mPlaybackSchedule.mTime ); + mPlaybackMixers[i]->Reposition( time ); const auto toDiscard = mPlaybackBuffers[i]->AvailForGet(); const auto discarded = @@ -5439,32 +5455,41 @@ void AudioIO::CallbackCheckCompletion( bool AudioIO::PlaybackSchedule::PassIsComplete() const { + // Test mTime within the PortAudio callback if (Scrubbing()) - return false; // but may be true if playing at speed - return (ReversedTime() ? mTime <= mT1 : mTime >= mT1); + return false; // but may be true if playing at speed + return Overruns( GetTrackTime() ); +} + +bool AudioIO::PlaybackSchedule::Overruns( double trackTime ) const +{ + return (ReversedTime() ? trackTime <= mT1 : trackTime >= mT1); } void AudioIO::PlaybackSchedule::TrackTimeUpdate(double realElapsed) { + // Update mTime within the PortAudio callback + if (Interactive()) return; if (ReversedTime()) realElapsed *= -1.0; + auto time = GetTrackTime(); if (mTimeTrack) { - // Defence against a case that might cause the do-loop not to terminate + // Defense against a case that might cause the do-loop not to terminate if ( fabs(mT0 - mT1) < 1e-9 ) { - mTime = mT0; + SetTrackTime( mT0 ); return; } double total; bool foundTotal = false; do { - auto oldTime = mTime; - mTime = mTimeTrack->SolveWarpedLength(mTime, realElapsed); - if (Looping() && PassIsComplete()) { + auto oldTime = time; + time = mTimeTrack->SolveWarpedLength(time, realElapsed); + if (Looping() && Overruns( time )) { // Bug1922: The part of the time track outside the loop should not // influence the result double delta; @@ -5477,25 +5502,26 @@ void AudioIO::PlaybackSchedule::TrackTimeUpdate(double realElapsed) foundTotal = true, total = delta; } realElapsed -= delta; - mTime = mT0; + time = mT0; } else break; } while ( true ); } else { - mTime += realElapsed; + time += realElapsed; // Wrap to start if looping if (Looping()) { - while (PassIsComplete()) { + while ( Overruns( time ) ) { // LL: This is not exactly right, but I'm at my wits end trying to // figure it out. Feel free to fix it. :-) // MB: it's much easier than you think, mTime isn't warped at all! - mTime -= mT1 - mT0; + time -= mT1 - mT0; } } } + SetTrackTime( time ); } double AudioIO::PlaybackSchedule::TrackDuration(double realElapsed) const @@ -5526,12 +5552,12 @@ void AudioIO::PlaybackSchedule::RealTimeAdvance( double increment ) mWarpedTime += increment; } -void AudioIO::PlaybackSchedule::RealTimeInit() +void AudioIO::PlaybackSchedule::RealTimeInit( double trackTime ) { if (Scrubbing()) mWarpedTime = 0.0; else - mWarpedTime = RealDuration(mTime); + mWarpedTime = RealDuration( trackTime ); } void AudioIO::PlaybackSchedule::RealTimeRestart() @@ -5556,7 +5582,8 @@ double AudioIO::RecordingSchedule::ToDiscard() const bool AudioIO::IsCapturing() const { + // Includes a test of mTime, used in the main thread return GetNumCaptureChannels() > 0 && - mPlaybackSchedule.mTime >= + mPlaybackSchedule.GetTrackTime() >= mPlaybackSchedule.mT0 + mRecordingSchedule.mPreRoll; } diff --git a/src/AudioIO.h b/src/AudioIO.h index 22cb33eef..6f51d2d79 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -18,6 +18,7 @@ #include "Experimental.h" #include "MemoryX.h" +#include #include #include #include @@ -832,8 +833,11 @@ private: double mT0; /// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing. double mT1; - /// Current time position during playback, in seconds. Between mT0 and mT1. - double mTime; + /// Current track time position during playback, in seconds. + /// Initialized by the main thread but updated by worker threads during + /// playback or recording, and periodically reread by the main thread for + /// purposes such as display update. + std::atomic mTime; /// Accumulated real time (not track position), starting at zero (unlike /// mTime), and wrapping back to zero each time around looping play. @@ -877,19 +881,38 @@ private: return mT1 < mT0; } + /** \brief Get current track time value, unadjusted + * + * Returns a time in seconds. + */ + double GetTrackTime() const + { return mTime.load(std::memory_order_relaxed); } + + /** \brief Set current track time value, unadjusted + */ + void SetTrackTime( double time ) + { mTime.store(time, std::memory_order_relaxed); } + + /** \brief Clamps argument to be between mT0 and mT1 + * + * Returns the bound if the value is out of bounds; does not wrap. + * Returns a time in seconds. + */ + double ClampTrackTime( double trackTime ) const; + /** \brief Clamps mTime to be between mT0 and mT1 * * Returns the bound if the value is out of bounds; does not wrap. * Returns a time in seconds. */ - double LimitStreamTime() const; + double LimitTrackTime() const; /** \brief Normalizes mTime, clamping it and handling gaps from cut preview. * * Clamps the time (unless scrubbing), and skips over the cut section. * Returns a time in seconds. */ - double NormalizeStreamTime() const; + double NormalizeTrackTime() const; void ResetMode() { mPlayMode = PLAY_STRAIGHT; } @@ -903,6 +926,9 @@ private: // is completed at the current value of mTime bool PassIsComplete() const; + // Returns true if time equals t1 or is on opposite side of t1, to t0 + bool Overruns( double trackTime ) const; + void TrackTimeUpdate(double realElapsed); // Convert a nonnegative real duration to an increment of track time @@ -921,7 +947,7 @@ private: // Determine starting duration within the first pass -- sometimes not // zero - void RealTimeInit(); + void RealTimeInit( double trackTime ); void RealTimeRestart();