mirror of
https://github.com/cookiengineer/audacity
synced 2025-10-25 15:53:52 +02:00
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.
This commit is contained in:
111
src/AudioIO.cpp
111
src/AudioIO.cpp
@@ -2124,6 +2124,12 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
||||
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);
|
||||
if (!mSilentScrub)
|
||||
{
|
||||
double startTime, endTime, speed;
|
||||
double startTime, endTime;
|
||||
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();
|
||||
if (mScrubDuration == 0)
|
||||
mScrubSpeed = 0;
|
||||
else
|
||||
mScrubSpeed =
|
||||
double(std::abs(diff)) / mScrubDuration.as_double();
|
||||
if (!mSilentScrub)
|
||||
{
|
||||
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,9 +5479,11 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
// Update the current time position if not scrubbing
|
||||
// (Already did it above, for scrubbing)
|
||||
|
||||
// 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.
|
||||
@@ -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)
|
||||
|
||||
@@ -807,6 +807,7 @@ private:
|
||||
std::unique_ptr<ScrubQueue> 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,6 +998,11 @@ private:
|
||||
size_t mIndex {};
|
||||
size_t mRemainder {};
|
||||
} mHead, mTail;
|
||||
|
||||
void Producer(
|
||||
const PlaybackSchedule &schedule, double rate, double scrubSpeed,
|
||||
size_t nSamples );
|
||||
double Consumer( size_t nSamples, double rate );
|
||||
} mTimeQueue;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user