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.
This commit is contained in:
Paul Licameli 2018-08-10 13:18:13 -04:00
parent 8a78ae280c
commit f036700b09
2 changed files with 98 additions and 45 deletions

View File

@ -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;
}

View File

@ -18,6 +18,7 @@
#include "Experimental.h"
#include "MemoryX.h"
#include <atomic>
#include <utility>
#include <vector>
#include <wx/atomic.h>
@ -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<double> 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();