mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-02 00:29:41 +02:00
Make scrubbing work when timer interval constant is shortened to 1 ms
* scrubbing: Allow scrub timer interval to be 1 ms, without bogus out-of-memory warnings. Less scrub lag: avoid redundant disk traffic in Mixer... Less scrub lag: don't poll for available data, get woken up directly
This commit is contained in:
commit
c3246e3f7b
@ -405,18 +405,28 @@ struct AudioIO::ScrubQueue
|
||||
double LastTimeInQueue() const
|
||||
{
|
||||
// Needed by the main thread sometimes
|
||||
wxCriticalSectionLocker locker(mUpdating);
|
||||
wxMutexLocker locker(mUpdating);
|
||||
const Entry &previous = mEntries[(mLeadingIdx + Size - 1) % Size];
|
||||
return previous.mS1 / mRate;
|
||||
}
|
||||
|
||||
void PoisonPill()
|
||||
{
|
||||
// Main thread is shutting down the scrubbing
|
||||
wxMutexLocker locker(mUpdating);
|
||||
mPoisoned = true;
|
||||
mAvailable.Signal();
|
||||
}
|
||||
|
||||
bool Producer(double end, double maxSpeed, bool bySpeed, bool maySkip)
|
||||
{
|
||||
wxASSERT(!mPoisoned);
|
||||
|
||||
// Main thread indicates a scrubbing interval
|
||||
|
||||
// MAY ADVANCE mLeadingIdx, BUT IT NEVER CATCHES UP TO mTrailingIdx.
|
||||
|
||||
wxCriticalSectionLocker locker(mUpdating);
|
||||
wxMutexLocker locker(mUpdating);
|
||||
const unsigned next = (mLeadingIdx + 1) % Size;
|
||||
if (next != mTrailingIdx)
|
||||
{
|
||||
@ -429,8 +439,10 @@ struct AudioIO::ScrubQueue
|
||||
const bool success =
|
||||
(InitEntry(mEntries[mLeadingIdx], startTime, end, maxSpeed,
|
||||
bySpeed, &previous, maySkip));
|
||||
if (success)
|
||||
if (success) {
|
||||
mLeadingIdx = next;
|
||||
mAvailable.Signal();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
else
|
||||
@ -450,7 +462,10 @@ struct AudioIO::ScrubQueue
|
||||
|
||||
// MAY ADVANCE mMiddleIdx, WHICH MAY EQUAL mLeadingIdx, BUT DOES NOT PASS IT.
|
||||
|
||||
wxCriticalSectionLocker locker(mUpdating);
|
||||
wxMutexLocker locker(mUpdating);
|
||||
while(!mPoisoned && mMiddleIdx == mLeadingIdx)
|
||||
mAvailable.Wait();
|
||||
|
||||
if (mMiddleIdx != mLeadingIdx)
|
||||
{
|
||||
// There is work in the queue
|
||||
@ -463,7 +478,8 @@ struct AudioIO::ScrubQueue
|
||||
}
|
||||
else
|
||||
{
|
||||
// next entry is not yet ready
|
||||
wxASSERT(mPoisoned);
|
||||
// We got the shut-down signal
|
||||
startSample = endSample = duration = -1L;
|
||||
}
|
||||
}
|
||||
@ -475,7 +491,7 @@ struct AudioIO::ScrubQueue
|
||||
|
||||
// MAY ADVANCE mTrailingIdx, BUT IT NEVER CATCHES UP TO mMiddleIdx.
|
||||
|
||||
wxCriticalSectionLocker locker(mUpdating);
|
||||
wxMutexLocker locker(mUpdating);
|
||||
|
||||
// Mark entries as partly or fully "consumed" for
|
||||
// purposes of mTime update. It should not happen that
|
||||
@ -678,7 +694,9 @@ private:
|
||||
const double mRate;
|
||||
const long mMinStutter;
|
||||
wxLongLong mLastScrubTimeMillis;
|
||||
mutable wxCriticalSection mUpdating;
|
||||
mutable wxMutex mUpdating;
|
||||
mutable wxCondition mAvailable { mUpdating };
|
||||
bool mPoisoned { false };
|
||||
};
|
||||
#endif
|
||||
|
||||
@ -1710,14 +1728,6 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
|
||||
sampleCount playbackMixBufferSize =
|
||||
(sampleCount)mPlaybackSamplesToCopy;
|
||||
|
||||
// In the extraordinarily rare case that we can't even afford 100 samples, just give up.
|
||||
if(playbackBufferSize < 100 || playbackMixBufferSize < 100)
|
||||
{
|
||||
StartStreamCleanup();
|
||||
wxMessageBox(_("Out of memory!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
mPlaybackBuffers = new RingBuffer* [mPlaybackTracks->size()];
|
||||
mPlaybackMixers = new Mixer* [mPlaybackTracks->size()];
|
||||
|
||||
@ -1786,7 +1796,19 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
|
||||
mCaptureRingBufferSecs *= 0.5;
|
||||
mMinCaptureSecsToCopy *= 0.5;
|
||||
bDone = false;
|
||||
}
|
||||
|
||||
// In the extraordinarily rare case that we can't even afford 100 samples, just give up.
|
||||
sampleCount playbackBufferSize =
|
||||
(sampleCount)lrint(mRate * mPlaybackRingBufferSecs);
|
||||
sampleCount playbackMixBufferSize =
|
||||
(sampleCount)mPlaybackSamplesToCopy;
|
||||
if(playbackBufferSize < 100 || playbackMixBufferSize < 100)
|
||||
{
|
||||
StartStreamCleanup();
|
||||
wxMessageBox(_("Out of memory!"));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} while(!bDone);
|
||||
|
||||
if (mNumPlaybackChannels > 0)
|
||||
@ -2166,6 +2188,8 @@ void AudioIO::StopStream()
|
||||
//
|
||||
|
||||
mAudioThreadFillBuffersLoopRunning = false;
|
||||
if (mScrubQueue)
|
||||
mScrubQueue->PoisonPill();
|
||||
|
||||
// Audacity can deadlock if it tries to update meters while
|
||||
// we're stopping PortAudio (because the meter updating code
|
||||
@ -2821,7 +2845,16 @@ AudioThread::ExitCode AudioThread::Entry()
|
||||
}
|
||||
gAudioIO->mAudioThreadFillBuffersLoopActive = false;
|
||||
|
||||
Sleep(10);
|
||||
if (gAudioIO->mPlayMode == AudioIO::PLAY_SCRUB) {
|
||||
// Rely on the Wait() in ScrubQueue::Transformer()
|
||||
// This allows the scrubbing update interval to be made very short without
|
||||
// playback becoming intermittent.
|
||||
}
|
||||
else {
|
||||
// Perhaps this too could use a condition variable, for available space in the
|
||||
// ring buffer, instead of a polling loop? But no harm in doing it this way.
|
||||
Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -650,7 +650,7 @@ private:
|
||||
bool mInputMixerWorks;
|
||||
float mMixerOutputVol;
|
||||
|
||||
enum {
|
||||
volatile enum {
|
||||
PLAY_STRAIGHT,
|
||||
PLAY_LOOPED,
|
||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||
|
45
src/Mix.cpp
45
src/Mix.cpp
@ -22,7 +22,6 @@
|
||||
|
||||
|
||||
#include "Audacity.h"
|
||||
|
||||
#include "Mix.h"
|
||||
|
||||
#include <math.h>
|
||||
@ -251,13 +250,13 @@ Mixer::Mixer(const WaveTrackConstArray &inputTracks,
|
||||
const auto numInputTracks = inputTracks.size();
|
||||
mHighQuality = highQuality;
|
||||
mNumInputTracks = numInputTracks;
|
||||
mInputTrack = new const WaveTrack*[mNumInputTracks];
|
||||
mInputTrack = new WaveTrackCache[mNumInputTracks];
|
||||
|
||||
// mSamplePos holds for each track the next sample position not
|
||||
// yet processed.
|
||||
mSamplePos = new sampleCount[mNumInputTracks];
|
||||
for(i=0; i<mNumInputTracks; i++) {
|
||||
mInputTrack[i] = inputTracks[i];
|
||||
mInputTrack[i].SetTrack(inputTracks[i]);
|
||||
mSamplePos[i] = inputTracks[i]->TimeToLongSamples(startTime);
|
||||
}
|
||||
mTimeTrack = warpOptions.timeTrack;
|
||||
@ -313,7 +312,7 @@ Mixer::Mixer(const WaveTrackConstArray &inputTracks,
|
||||
mSampleQueue = new float *[mNumInputTracks];
|
||||
mResample = new Resample*[mNumInputTracks];
|
||||
for(i=0; i<mNumInputTracks; i++) {
|
||||
double factor = (mRate / mInputTrack[i]->GetRate());
|
||||
double factor = (mRate / mInputTrack[i].GetTrack()->GetRate());
|
||||
double minFactor, maxFactor;
|
||||
if (mTimeTrack) {
|
||||
// variable rate resampling
|
||||
@ -408,11 +407,12 @@ void MixBuffers(int numChannels, int *channelFlags, float *gains,
|
||||
}
|
||||
}
|
||||
|
||||
sampleCount Mixer::MixVariableRates(int *channelFlags, const WaveTrack *track,
|
||||
sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrackCache &cache,
|
||||
sampleCount *pos, float *queue,
|
||||
int *queueStart, int *queueLen,
|
||||
Resample * pResample)
|
||||
{
|
||||
const WaveTrack *const track = cache.GetTrack();
|
||||
const double trackRate = track->GetRate();
|
||||
const double initialWarp = mRate / mSpeed / trackRate;
|
||||
const double tstep = 1.0 / trackRate;
|
||||
@ -455,10 +455,8 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, const WaveTrack *track,
|
||||
// Nothing to do if past end of play interval
|
||||
if (getLen > 0) {
|
||||
if (backwards) {
|
||||
track->Get((samplePtr)&queue[*queueLen],
|
||||
floatSample,
|
||||
*pos - (getLen - 1),
|
||||
getLen);
|
||||
auto results = cache.Get(floatSample, *pos - (getLen - 1), getLen);
|
||||
memcpy(&queue[*queueLen], results, sizeof(float) * getLen);
|
||||
|
||||
track->GetEnvelopeValues(mEnvValues,
|
||||
getLen,
|
||||
@ -468,10 +466,8 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, const WaveTrack *track,
|
||||
*pos -= getLen;
|
||||
}
|
||||
else {
|
||||
track->Get((samplePtr)&queue[*queueLen],
|
||||
floatSample,
|
||||
*pos,
|
||||
getLen);
|
||||
auto results = cache.Get(floatSample, *pos, getLen);
|
||||
memcpy(&queue[*queueLen], results, sizeof(float) * getLen);
|
||||
|
||||
track->GetEnvelopeValues(mEnvValues,
|
||||
getLen,
|
||||
@ -558,9 +554,10 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, const WaveTrack *track,
|
||||
return out;
|
||||
}
|
||||
|
||||
sampleCount Mixer::MixSameRate(int *channelFlags, const WaveTrack *track,
|
||||
sampleCount Mixer::MixSameRate(int *channelFlags, WaveTrackCache &cache,
|
||||
sampleCount *pos)
|
||||
{
|
||||
const WaveTrack *const track = cache.GetTrack();
|
||||
int slen = mMaxOut;
|
||||
int c;
|
||||
const double t = *pos / track->GetRate();
|
||||
@ -588,7 +585,8 @@ sampleCount Mixer::MixSameRate(int *channelFlags, const WaveTrack *track,
|
||||
slen = mMaxOut;
|
||||
|
||||
if (backwards) {
|
||||
track->Get((samplePtr)mFloatBuffer, floatSample, *pos - (slen - 1), slen);
|
||||
auto results = cache.Get(floatSample, *pos - (slen - 1), slen);
|
||||
memcpy(mFloatBuffer, results, sizeof(float) * slen);
|
||||
track->GetEnvelopeValues(mEnvValues, slen, t - (slen - 1) / mRate, 1.0 / mRate);
|
||||
for(int i=0; i<slen; i++)
|
||||
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
|
||||
@ -597,7 +595,8 @@ sampleCount Mixer::MixSameRate(int *channelFlags, const WaveTrack *track,
|
||||
*pos -= slen;
|
||||
}
|
||||
else {
|
||||
track->Get((samplePtr)mFloatBuffer, floatSample, *pos, slen);
|
||||
auto results = cache.Get(floatSample, *pos, slen);
|
||||
memcpy(mFloatBuffer, results, sizeof(float) * slen);
|
||||
track->GetEnvelopeValues(mEnvValues, slen, t, 1.0 / mRate);
|
||||
for(int i=0; i<slen; i++)
|
||||
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
|
||||
@ -632,7 +631,7 @@ sampleCount Mixer::Process(sampleCount maxToProcess)
|
||||
|
||||
Clear();
|
||||
for(i=0; i<mNumInputTracks; i++) {
|
||||
const WaveTrack *track = mInputTrack[i];
|
||||
const WaveTrack *const track = mInputTrack[i].GetTrack();
|
||||
for(j=0; j<mNumChannels; j++)
|
||||
channelFlags[j] = 0;
|
||||
|
||||
@ -661,12 +660,12 @@ sampleCount Mixer::Process(sampleCount maxToProcess)
|
||||
}
|
||||
if (mbVariableRates || track->GetRate() != mRate)
|
||||
maxOut = std::max(maxOut,
|
||||
MixVariableRates(channelFlags, track,
|
||||
&mSamplePos[i], mSampleQueue[i],
|
||||
&mQueueStart[i], &mQueueLen[i], mResample[i]));
|
||||
MixVariableRates(channelFlags, mInputTrack[i],
|
||||
&mSamplePos[i], mSampleQueue[i],
|
||||
&mQueueStart[i], &mQueueLen[i], mResample[i]));
|
||||
else
|
||||
maxOut = std::max(maxOut,
|
||||
MixSameRate(channelFlags, track, &mSamplePos[i]));
|
||||
MixSameRate(channelFlags, mInputTrack[i], &mSamplePos[i]));
|
||||
|
||||
double t = (double)mSamplePos[i] / (double)track->GetRate();
|
||||
if (mT0 > mT1)
|
||||
@ -728,7 +727,7 @@ void Mixer::Restart()
|
||||
mTime = mT0;
|
||||
|
||||
for(i=0; i<mNumInputTracks; i++)
|
||||
mSamplePos[i] = mInputTrack[i]->TimeToLongSamples(mT0);
|
||||
mSamplePos[i] = mInputTrack[i].GetTrack()->TimeToLongSamples(mT0);
|
||||
|
||||
for(i=0; i<mNumInputTracks; i++) {
|
||||
mQueueStart[i] = 0;
|
||||
@ -748,7 +747,7 @@ void Mixer::Reposition(double t)
|
||||
mTime = std::max(mT0, (std::min(mT1, mTime)));
|
||||
|
||||
for(i=0; i<mNumInputTracks; i++) {
|
||||
mSamplePos[i] = mInputTrack[i]->TimeToLongSamples(mTime);
|
||||
mSamplePos[i] = mInputTrack[i].GetTrack()->TimeToLongSamples(mTime);
|
||||
mQueueStart[i] = 0;
|
||||
mQueueLen[i] = 0;
|
||||
}
|
||||
|
11
src/Mix.h
11
src/Mix.h
@ -14,16 +14,16 @@
|
||||
|
||||
#include "MemoryX.h"
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "SampleFormat.h"
|
||||
#include "Resample.h"
|
||||
|
||||
class Resample;
|
||||
class DirManager;
|
||||
class TimeTrack;
|
||||
class TrackFactory;
|
||||
class TrackList;
|
||||
class WaveTrack;
|
||||
class WaveTrackConstArray;
|
||||
class WaveTrackCache;
|
||||
|
||||
/** @brief Mixes together all input tracks, applying any envelopes, amplitude
|
||||
* gain, panning, and real-time effects in the process.
|
||||
@ -141,10 +141,10 @@ class AUDACITY_DLL_API Mixer {
|
||||
private:
|
||||
|
||||
void Clear();
|
||||
sampleCount MixSameRate(int *channelFlags, const WaveTrack *src,
|
||||
sampleCount MixSameRate(int *channelFlags, WaveTrackCache &cache,
|
||||
sampleCount *pos);
|
||||
|
||||
sampleCount MixVariableRates(int *channelFlags, const WaveTrack *track,
|
||||
sampleCount MixVariableRates(int *channelFlags, WaveTrackCache &cache,
|
||||
sampleCount *pos, float *queue,
|
||||
int *queueStart, int *queueLen,
|
||||
Resample * pResample);
|
||||
@ -152,7 +152,8 @@ class AUDACITY_DLL_API Mixer {
|
||||
private:
|
||||
// Input
|
||||
int mNumInputTracks;
|
||||
const WaveTrack **mInputTrack;
|
||||
WaveTrackCache *mInputTrack;
|
||||
|
||||
bool mbVariableRates;
|
||||
const TimeTrack *mTimeTrack;
|
||||
sampleCount *mSamplePos;
|
||||
|
Loading…
x
Reference in New Issue
Block a user