From 67a98234345468da1c768a78c12b7e14c1d80129 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 11 Aug 2018 23:41:45 -0400 Subject: [PATCH 1/5] split out function AdvancedTrackTime; more caution about termination --- src/AudioIO.cpp | 36 ++++++++++++++++++++++-------------- src/AudioIO.h | 7 +++++++ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 05422afec..64713bf37 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -5646,23 +5646,18 @@ bool AudioIO::PlaybackSchedule::Overruns( double trackTime ) const return (ReversedTime() ? trackTime <= mT1 : trackTime >= mT1); } -void AudioIO::PlaybackSchedule::TrackTimeUpdate(double realElapsed) +double AudioIO::PlaybackSchedule::AdvancedTrackTime( + double time, double realElapsed, double speed ) const { - // Update mTime within the PortAudio callback - - if (Interactive()) - return; - if (ReversedTime()) realElapsed *= -1.0; - auto time = GetTrackTime(); + // Defense against cases that might cause loops not to terminate + if ( fabs(mT0 - mT1) < 1e-9 ) + return mT0; + if (mTimeTrack) { - // Defense against a case that might cause the do-loop not to terminate - if ( fabs(mT0 - mT1) < 1e-9 ) { - SetTrackTime( mT0 ); - return; - } + wxASSERT( speed == 1.0 ); double total; bool foundTotal = false; @@ -5693,7 +5688,7 @@ void AudioIO::PlaybackSchedule::TrackTimeUpdate(double realElapsed) } while ( true ); } else { - time += realElapsed; + time += realElapsed * speed; // Wrap to start if looping if (Looping()) { @@ -5705,7 +5700,20 @@ void AudioIO::PlaybackSchedule::TrackTimeUpdate(double realElapsed) } } } - SetTrackTime( time ); + + return time; +} + +void AudioIO::PlaybackSchedule::TrackTimeUpdate(double realElapsed) +{ + // Update mTime within the PortAudio callback + + if (Interactive()) + return; + + auto time = GetTrackTime(); + auto newTime = AdvancedTrackTime( time, realElapsed, 1.0 ); + SetTrackTime( newTime ); } double AudioIO::PlaybackSchedule::TrackDuration(double realElapsed) const diff --git a/src/AudioIO.h b/src/AudioIO.h index c0e684354..7d211e951 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -954,6 +954,13 @@ private: // Returns true if time equals t1 or is on opposite side of t1, to t0 bool Overruns( double trackTime ) const; + // Compute the NEW track time for the given one and a real duration, + // taking into account whether the schedule is for looping + double AdvancedTrackTime( + double trackTime, double realElapsed, double speed) const; + + // Use the function above in the callback after consuming samples from the + // playback ring buffers, during usual straight or looping play void TrackTimeUpdate(double realElapsed); // Convert a nonnegative real duration to an increment of track time From b2df5e06730655afc7a9bd2c0dd48a353dd350a5 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 11 Aug 2018 23:56:54 -0400 Subject: [PATCH 2/5] Declare and allocate time value queue --- src/AudioIO.cpp | 12 ++++++++++++ src/AudioIO.h | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 64713bf37..88f7c8f14 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -503,6 +503,8 @@ enum { MIDI_MINIMAL_LATENCY_MS = 1 }; +constexpr size_t TimeQueueGrainSize = 2000; + #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #include "tracks/ui/Scrubbing.h" @@ -2001,6 +2003,7 @@ int AudioIO::StartStream(const TransportTracks &tracks, mPlaybackMixers.reset(); mCaptureBuffers.reset(); mResample.reset(); + mTimeQueue.mData.reset(); #ifdef EXPERIMENTAL_MIDI_OUT streamStartTime = 0; @@ -2277,6 +2280,8 @@ bool AudioIO::AllocateBuffers( mMinCaptureSecsToCopy = 0.2 + 0.2 * std::min(size_t(16), mCaptureTracks.size()); + mTimeQueue.mHead = {}; + mTimeQueue.mTail = {}; bool bDone; do { @@ -2321,6 +2326,11 @@ bool AudioIO::AllocateBuffers( mPlaybackBuffers[i] = std::make_unique(floatSample, playbackBufferSize); + const auto timeQueueSize = + (playbackBufferSize + TimeQueueGrainSize - 1) + / TimeQueueGrainSize; + mTimeQueue.mData.reinit( timeQueueSize ); + mTimeQueue.mSize = timeQueueSize; // use track time for the end time, not real time! WaveTrackConstArray mixTracks; @@ -2419,6 +2429,7 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers) mPlaybackMixers.reset(); mCaptureBuffers.reset(); mResample.reset(); + mTimeQueue.mData.reset(); if(!bOnlyBuffers) { @@ -2767,6 +2778,7 @@ void AudioIO::StopStream() { mPlaybackBuffers.reset(); mPlaybackMixers.reset(); + mTimeQueue.mData.reset(); } // diff --git a/src/AudioIO.h b/src/AudioIO.h index 7d211e951..e59c876bc 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -984,6 +984,21 @@ private: void RealTimeRestart(); } mPlaybackSchedule; + + // Another circular buffer + // Holds track time values corresponding to every nth sample in the playback + // buffers, for some large n + struct TimeQueue { + Doubles mData; + size_t mSize{ 0 }; + // These need not be updated atomically, because we rely on the atomics + // in the playback ring buffers to supply the synchronization. Still, + // align them to avoid false sharing. + alignas(64) struct Cursor { + size_t mIndex {}; + size_t mRemainder {}; + } mHead, mTail; + } mTimeQueue; }; #endif From 389ab0c8d08d9b328a74ce4459b62ee1939059f9 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Mon, 13 Aug 2018 18:05:11 -0400 Subject: [PATCH 3/5] Fewer calls to RingBuffer::Put in case of trailing zeroes... ... so there is only one update per track of the atomics in RingBuffer in each pass of the loop in FillBuffers, which will be needed to synchronize RingBuffer and TimeQueue correctly. --- src/AudioIO.cpp | 55 ++++++++++++++-------------------------------- src/AudioIO.h | 2 -- src/RingBuffer.cpp | 16 +++++++++++--- src/RingBuffer.h | 4 +++- 4 files changed, 33 insertions(+), 44 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 88f7c8f14..f0c47d226 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -3987,10 +3987,12 @@ void AudioIO::FillBuffers() // How many samples to produce for each channel. auto frames = available; bool progress = true; + auto toProcess = frames; #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT if (mPlaybackSchedule.Interactive()) // scrubbing and play-at-speed are not limited by the real time // and length accumulators + toProcess = frames = limitSampleBufferSize(frames, mScrubDuration); else #endif @@ -3999,6 +4001,7 @@ void AudioIO::FillBuffers() if (deltat > realTimeRemaining) { frames = realTimeRemaining * mRate; + toProcess = frames; // Don't fall into an infinite loop, if loop-playing a selection // that is so short, it has no samples: detect that case progress = @@ -4012,56 +4015,32 @@ void AudioIO::FillBuffers() } if (!progress) - frames = available; + frames = available, toProcess = 0; +#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT + else if ( mPlaybackSchedule.Interactive() && mSilentScrub) + toProcess = 0; +#endif for (i = 0; i < mPlaybackTracks.size(); i++) { // The mixer here isn't actually mixing: it's just doing // resampling, format conversion, and possibly time track // warping - decltype(mPlaybackMixers[i]->Process(frames)) - processed = 0; samplePtr warpedSamples; - //don't do anything if we have no length. In particular, Process() will fail an wxAssert - //that causes a crash since this is not the GUI thread and wxASSERT is a GUI call. - // don't generate either if scrubbing at zero speed. -#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - const bool silent = - mPlaybackSchedule.Interactive() && mSilentScrub; -#else - const bool silent = false; -#endif - - if (progress && !silent && frames > 0) + if (frames > 0) { - processed = mPlaybackMixers[i]->Process(frames); - wxASSERT(processed <= frames); + size_t processed = 0; + if ( toProcess ) + processed = mPlaybackMixers[i]->Process( toProcess ); + //wxASSERT(processed <= toProcess); warpedSamples = mPlaybackMixers[i]->GetBuffer(); - const auto put = mPlaybackBuffers[i]->Put - (warpedSamples, floatSample, processed); - // wxASSERT(put == processed); + const auto put = mPlaybackBuffers[i]->Put( + warpedSamples, floatSample, processed, frames - processed); + // wxASSERT(put == frames); // but we can't assert in this thread wxUnusedVar(put); - } - - //if looping and processed is less than the full chunk/block/buffer that gets pulled from - //other longer tracks, then we still need to advance the ring buffers or - //we'll trip up on ourselves when we start them back up again. - //if not looping we never start them up again, so its okay to not do anything - // If scrubbing, we may be producing some silence. Otherwise this should not happen, - // but makes sure anyway that we produce equal - // numbers of samples for all channels for this pass of the do-loop. - if(processed < frames && !mPlaybackSchedule.PlayingStraight()) - { - mSilentBuf.Resize(frames, floatSample); - ClearSamples(mSilentBuf.ptr(), floatSample, 0, frames); - const auto put = mPlaybackBuffers[i]->Put - (mSilentBuf.ptr(), floatSample, frames - processed); - // wxASSERT(put == frames - processed); - // but we can't assert in this thread - wxUnusedVar(put); - } + } } available -= frames; diff --git a/src/AudioIO.h b/src/AudioIO.h index e59c876bc..08b78a84d 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -778,8 +778,6 @@ private: bool mInputMixerWorks; float mMixerOutputVol; - GrowableSampleBuffer mSilentBuf; - AudioIOListener* mListener; friend class AudioThread; diff --git a/src/RingBuffer.cpp b/src/RingBuffer.cpp index 50c204732..2020cd521 100644 --- a/src/RingBuffer.cpp +++ b/src/RingBuffer.cpp @@ -69,16 +69,18 @@ size_t RingBuffer::AvailForPut() } size_t RingBuffer::Put(samplePtr buffer, sampleFormat format, - size_t samplesToCopy) + size_t samplesToCopy, size_t padding) { auto start = mStart.load( std::memory_order_acquire ); auto end = mEnd.load( std::memory_order_relaxed ); - samplesToCopy = std::min( samplesToCopy, Free( start, end ) ); + const auto free = Free( start, end ); + samplesToCopy = std::min( samplesToCopy, free ); + padding = std::min( padding, free - samplesToCopy ); auto src = buffer; size_t copied = 0; auto pos = end; - while(samplesToCopy) { + while ( samplesToCopy ) { auto block = std::min( samplesToCopy, mBufferSize - pos ); CopySamples(src, format, @@ -91,6 +93,14 @@ size_t RingBuffer::Put(samplePtr buffer, sampleFormat format, copied += block; } + while ( padding ) { + const auto block = std::min( padding, mBufferSize - pos ); + ClearSamples( mBuffer.ptr(), mFormat, pos, block ); + pos = (pos + block) % mBufferSize; + padding -= block; + copied += block; + } + // Atomically update the end pointer with release, so the nonatomic writes // just done to the buffer don't get reordered after mEnd.store(pos, std::memory_order_release); diff --git a/src/RingBuffer.h b/src/RingBuffer.h index 4f993a2ff..dad54b709 100644 --- a/src/RingBuffer.h +++ b/src/RingBuffer.h @@ -24,7 +24,9 @@ class RingBuffer { // size_t AvailForPut(); - size_t Put(samplePtr buffer, sampleFormat format, size_t samples); + size_t Put(samplePtr buffer, sampleFormat format, size_t samples, + // optional number of trailing zeroes + size_t padding = 0); size_t Clear(sampleFormat format, size_t samples); // From 81a3a7d339ead13910d8afad830104b5ae9b0303 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sun, 12 Aug 2018 08:24:45 -0400 Subject: [PATCH 4/5] Use the TimeQueue to communicate play head position updates for scrubbing... ... from the Audio thread to the PortAudio thread; the old ScrubQueue::Consumer() function keeps only a vestigial purpose to prevent the scrub queue from blocking. --- src/AudioIO.cpp | 115 +++++++++++++++++++++++++++++++++++++++++------- src/AudioIO.h | 9 +++- 2 files changed, 107 insertions(+), 17 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index f0c47d226..ccaf8621d 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -2123,6 +2123,12 @@ int AudioIO::StartStream(const TransportTracks &tracks, mPlaybackMixers[ii]->Reposition( time ); mPlaybackSchedule.RealTimeInit( time ); } + + // Now that we are done with SetTrackTime(): + mTimeQueue.mLastTime = mPlaybackSchedule.GetTrackTime(); + if (mTimeQueue.mData) + mTimeQueue.mData[0] = mTimeQueue.mLastTime; + // else recording only without overdub #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT if (scrubbing) @@ -4021,6 +4027,15 @@ void AudioIO::FillBuffers() toProcess = 0; #endif + // Update the time queue. This must be done before writing to the + // ring buffers of samples, for proper synchronization with the + // consumer side in the PortAudio thread, which reads the time + // queue after reading the sample queues. The sample queues use + // atomic variables, the time queue doesn't. + mTimeQueue.Producer( mPlaybackSchedule, mRate, + (mPlaybackSchedule.Interactive() ? mScrubSpeed : 1.0), + frames); + for (i = 0; i < mPlaybackTracks.size(); i++) { // The mixer here isn't actually mixing: it's just doing @@ -4069,16 +4084,22 @@ void AudioIO::FillBuffers() else { mSilentScrub = (endSample == startSample); + double startTime, endTime; + startTime = startSample.as_double() / mRate; + endTime = endSample.as_double() / mRate; + auto diff = (endSample - startSample).as_long_long(); + if (mScrubDuration == 0) + mScrubSpeed = 0; + else + mScrubSpeed = + double(std::abs(diff)) / mScrubDuration.as_double(); if (!mSilentScrub) { - double startTime, endTime, speed; - startTime = startSample.as_double() / mRate; - endTime = endSample.as_double() / mRate; - auto diff = (endSample - startSample).as_long_long(); - speed = double(std::abs(diff)) / mScrubDuration.as_double(); for (i = 0; i < mPlaybackTracks.size(); i++) - mPlaybackMixers[i]->SetTimesAndSpeed(startTime, endTime, speed); + mPlaybackMixers[i]->SetTimesAndSpeed( + startTime, endTime, mScrubSpeed); } + mTimeQueue.mLastTime = startTime; } } } @@ -5046,6 +5067,8 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer, if (mStreamToken > 0) { + decltype(framesPerBuffer) maxLen = 0; + // // Mix and copy to PortAudio's output buffer // @@ -5118,7 +5141,6 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer, bool selected = false; int group = 0; int chanCnt = 0; - decltype(framesPerBuffer) maxLen = 0; // Choose a common size to take from all ring buffers const auto toGet = @@ -5200,6 +5222,10 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer, // PRL: Bug1104: // There can be a difference of len in different loop passes if one channel // of a stereo track ends before the other! Take a max! + + // PRL: More recent rewrites of FillBuffers should guarantee a + // padding out of the ring buffers so that equal lengths are + // available, so maxLen ought to increase from 0 only once maxLen = std::max(maxLen, len); @@ -5231,7 +5257,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer, } #endif - // Last channel seen now + // Last channel of a track seen now len = maxLen; if( !cutQuickly && selected ) @@ -5316,12 +5342,10 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer, CallbackCheckCompletion(callbackReturn, 0); #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - // Update the current time position, for scrubbing - // "Consume" only as much as the ring buffers produced, which may - // be less than framesPerBuffer (during "stutter") // wxASSERT( maxLen == toGet ); if (mPlaybackSchedule.Interactive()) - mPlaybackSchedule.SetTrackTime( mScrubQueue->Consumer( maxLen ) ); + // Just do this to free space in scrub queue + mScrubQueue->Consumer( maxLen ); #endif em.RealtimeProcessEnd(); @@ -5455,10 +5479,12 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer, } } - // Update the current time position if not scrubbing - // (Already did it above, for scrubbing) - - mPlaybackSchedule.TrackTimeUpdate( framesPerBuffer / mRate ); + // Update the position seen by drawing code + if (mPlaybackSchedule.Interactive()) + // To do: do this in all cases and remove TrackTimeUpdate + mPlaybackSchedule.SetTrackTime( mTimeQueue.Consumer( maxLen, mRate ) ); + else + mPlaybackSchedule.TrackTimeUpdate( framesPerBuffer / mRate ); // Record the reported latency from PortAudio. // TODO: Don't recalculate this with every callback? @@ -5707,6 +5733,63 @@ void AudioIO::PlaybackSchedule::TrackTimeUpdate(double realElapsed) SetTrackTime( newTime ); } +void AudioIO::TimeQueue::Producer( + const PlaybackSchedule &schedule, double rate, double scrubSpeed, + size_t nSamples ) +{ + if ( ! mData ) + // Recording only. Don't fill the queue. + return; + + // Don't check available space: assume it is enough because of coordination + // with RingBuffer. + auto index = mTail.mIndex; + auto time = mLastTime; + auto remainder = mTail.mRemainder; + auto space = TimeQueueGrainSize - remainder; + + while ( nSamples >= space ) { + time = schedule.AdvancedTrackTime( time, space / rate, scrubSpeed ); + index = (index + 1) % mSize; + mData[ index ] = time; + nSamples -= space; + remainder = 0; + space = TimeQueueGrainSize; + } + + // Last odd lot + if ( nSamples > 0 ) + time = schedule.AdvancedTrackTime( time, nSamples / rate, scrubSpeed ); + + mLastTime = time; + mTail.mRemainder = remainder + nSamples; + mTail.mIndex = index; +} + +double AudioIO::TimeQueue::Consumer( size_t nSamples, double rate ) +{ + if ( ! mData ) { + // Recording only. No scrub or playback time warp. Don't use the queue. + return ( mLastTime += nSamples / rate ); + } + + // Don't check available space: assume it is enough because of coordination + // with RingBuffer. + auto remainder = mHead.mRemainder; + auto space = TimeQueueGrainSize - remainder; + if ( nSamples >= space ) { + remainder = 0, + mHead.mIndex = (mHead.mIndex + 1) % mSize, + nSamples -= space; + if ( nSamples >= TimeQueueGrainSize ) + mHead.mIndex = + (mHead.mIndex + ( nSamples / TimeQueueGrainSize ) ) % mSize, + nSamples %= TimeQueueGrainSize; + } + mHead.mRemainder = remainder + nSamples; + return mData[ mHead.mIndex ]; +} + double AudioIO::PlaybackSchedule::TrackDuration(double realElapsed) const { if (mTimeTrack) diff --git a/src/AudioIO.h b/src/AudioIO.h index 08b78a84d..46b96b446 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -807,6 +807,7 @@ private: std::unique_ptr mScrubQueue; bool mSilentScrub; + double mScrubSpeed; sampleCount mScrubDuration; #endif @@ -989,6 +990,7 @@ private: struct TimeQueue { Doubles mData; size_t mSize{ 0 }; + double mLastTime {}; // These need not be updated atomically, because we rely on the atomics // in the playback ring buffers to supply the synchronization. Still, // align them to avoid false sharing. @@ -996,7 +998,12 @@ private: size_t mIndex {}; size_t mRemainder {}; } mHead, mTail; - } mTimeQueue; + + void Producer( + const PlaybackSchedule &schedule, double rate, double scrubSpeed, + size_t nSamples ); + double Consumer( size_t nSamples, double rate ); + } mTimeQueue; }; #endif From 69435c9191bbcce2c4ab6490054395aaf2798943 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Mon, 13 Aug 2018 15:29:42 -0400 Subject: [PATCH 5/5] Simplify ScrubQueue to involve only two threads... ... Remove Consumer() and fields used only by it; rename Transformer() as Consumer(). A bit less contention for the scrub queue mutex, then. --- src/AudioIO.cpp | 98 ++++++++----------------------------------------- 1 file changed, 15 insertions(+), 83 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index ccaf8621d..800766d02 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -523,25 +523,18 @@ constexpr size_t TimeQueueGrainSize = 2000; /* -This work queue class, with the aid of the playback ring -buffers, coordinates three threads during scrub play: +This work queue class coordinates two threads during scrub play: The UI thread which specifies scrubbing intervals to play, -The Audio thread which consumes those specifications a first time -and fills the ring buffers with samples for play, - -The PortAudio thread which consumes from the ring buffers, then -also consumes a second time from this queue, -to figure out how to update mTime - --- which the UI thread, in turn, uses to redraw the play head indicator -in the right place. +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 the others +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 the others -- because InitEntry() +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. */ @@ -571,13 +564,6 @@ struct AudioIO::ScrubQueue // If not, we can wait to enqueue again later dd.Cancel(); } - - // So the play indicator starts out unconfused: - { - Entry &entry = mEntries[mTrailingIdx]; - entry.mS0 = entry.mS1 = s0; - entry.mPlayed = entry.mDuration = 1; - } } ~ScrubQueue() {} @@ -656,16 +642,16 @@ struct AudioIO::ScrubQueue { // ?? // Queue wasn't long enough. Write side (UI thread) - // has overtaken the trailing read side (PortAudio thread), despite + // has overtaken the trailing read side (Audio thread), despite // my comments above! We lose some work requests then. // wxASSERT(false); return false; } } - void Transformer(sampleCount &startSample, sampleCount &endSample, - sampleCount &duration, - Maybe &cleanup) + void Consumer(sampleCount &startSample, sampleCount &endSample, + sampleCount &duration, + Maybe &cleanup) { // Audio thread is ready for the next interval. @@ -706,7 +692,8 @@ struct AudioIO::ScrubQueue // Discard entire queue entry mDebt -= dur; toDiscard -= dur; - dur = 0; // So Consumer() will handle abandoned entry correctly + dur = 0; + mTrailingIdx = mMiddleIdx; mMiddleIdx = (mMiddleIdx + 1) % Size; } else { @@ -735,6 +722,7 @@ struct AudioIO::ScrubQueue startSample = entry.mS0; endSample = entry.mS1; duration = entry.mDuration; + mTrailingIdx = mMiddleIdx; mMiddleIdx = (mMiddleIdx + 1) % Size; mCredit += duration; } @@ -747,42 +735,6 @@ struct AudioIO::ScrubQueue mLastTransformerTimeMillis = now; } - double Consumer(unsigned long frames) - { - // Portaudio thread consumes samples and must update - // the time for the indicator. This finds the time value. - - // MAY ADVANCE mTrailingIdx, BUT IT NEVER CATCHES UP TO mMiddleIdx. - - wxMutexLocker locker(mUpdating); - - // Mark entries as partly or fully "consumed" for - // purposes of mTime update. It should not happen that - // frames exceed the total of samples to be consumed, - // but in that case we just use the t1 of the latest entry. - while (1) - { - Entry *pEntry = &mEntries[mTrailingIdx]; - auto remaining = pEntry->mDuration - pEntry->mPlayed; - if (frames >= remaining) - { - // remaining is not more than frames - frames -= remaining.as_size_t(); - pEntry->mPlayed = pEntry->mDuration; - } - else - { - pEntry->mPlayed += frames; - break; - } - const unsigned next = (mTrailingIdx + 1) % Size; - if (next == mMiddleIdx) - break; - mTrailingIdx = next; - } - return mEntries[mTrailingIdx].GetTime(mRate); - } - private: struct Entry { @@ -791,7 +743,6 @@ private: , mS1(0) , mGoal(0) , mDuration(0) - , mPlayed(0) {} bool Init(Entry *previous, sampleCount s0, sampleCount s1, @@ -914,7 +865,6 @@ private: mS0 = s0; mS1 = s1; - mPlayed = 0; mDuration = duration; return true; } @@ -923,18 +873,9 @@ private: { mGoal = previous.mGoal; mS0 = mS1 = previous.mS1; - mPlayed = 0; mDuration = duration; } - double GetTime(double rate) const - { - return - (mS0.as_double() + - (mS1 - mS0).as_double() * mPlayed.as_double() / mDuration.as_double()) - / rate; - } - // These sample counts are initialized in the UI, producer, thread: sampleCount mS0; sampleCount mS1; @@ -946,10 +887,6 @@ private: // The middleman Audio thread does not change these entries, but only // changes indices in the queue structure. - - // This increases from 0 to mDuration as the PortAudio, consumer, - // thread catches up. When they are equal, this entry can be discarded: - sampleCount mPlayed; }; struct Duration { @@ -3383,7 +3320,7 @@ AudioThread::ExitCode AudioThread::Entry() gAudioIO->mAudioThreadFillBuffersLoopActive = false; if (gAudioIO->mPlaybackSchedule.Interactive()) { - // Rely on the Wait() in ScrubQueue::Transformer() + // Rely on the Wait() in ScrubQueue::Consumer() // This allows the scrubbing update interval to be made very short without // playback becoming intermittent. } @@ -4073,7 +4010,7 @@ void AudioIO::FillBuffers() if (!done && mScrubDuration <= 0) { sampleCount startSample, endSample; - mScrubQueue->Transformer(startSample, endSample, mScrubDuration, cleanup); + mScrubQueue->Consumer(startSample, endSample, mScrubDuration, cleanup); if (mScrubDuration < 0) { // Can't play anything @@ -5341,12 +5278,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer, if (numPlaybackTracks == 0) CallbackCheckCompletion(callbackReturn, 0); -#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT // wxASSERT( maxLen == toGet ); - if (mPlaybackSchedule.Interactive()) - // Just do this to free space in scrub queue - mScrubQueue->Consumer( maxLen ); -#endif em.RealtimeProcessEnd();