1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-09-17 16:50:26 +02:00

Merge Fix display caching for waveforms and spectrograms so smooth scrolling works

This will matter if the smooth-scrolling version of scrubbing is enabled

The problem was accumulation of roundoff errors when deciding which pixel
column data to copy from old caches to new caches, when the number of samples
scrolled was not an integer multiple of samples per pixel (itself, not always
an integer)

Thus repeated scrolling by small amounts caused the wave display or spectrogram
to creep relative to the time ruler!
This commit is contained in:
Paul-Licameli 2015-04-17 11:38:53 -04:00
commit c65570c465

View File

@ -48,11 +48,12 @@ public:
start = -1.0; start = -1.0;
pps = 0.0; pps = 0.0;
len = cacheLen; len = cacheLen;
min = new float[len]; min = len ? new float[len] : 0;
max = new float[len]; max = len ? new float[len] : 0;
rms = new float[len]; rms = len ? new float[len] : 0;
bl = new int[len]; bl = len ? new int[len] : 0;
where = new sampleCount[len+1]; where = new sampleCount[len+1];
where[0] = 0;
numODPixels=0; numODPixels=0;
} }
@ -233,8 +234,9 @@ public:
pps = 0.0; pps = 0.0;
len = cacheLen; len = cacheLen;
ac = autocorrelation; ac = autocorrelation;
freq = new float[len*half]; freq = len ? new float[len*half] : 0;
where = new sampleCount[len+1]; where = new sampleCount[len+1];
where[0] = 0;
} }
~SpecCache() ~SpecCache()
@ -297,14 +299,14 @@ WaveClip::WaveClip(DirManager *projDirManager, sampleFormat format, int rate)
mRate = rate; mRate = rate;
mSequence = new Sequence(projDirManager, format); mSequence = new Sequence(projDirManager, format);
mEnvelope = new Envelope(); mEnvelope = new Envelope();
mWaveCache = new WaveCache(1); mWaveCache = new WaveCache(0);
#ifdef EXPERIMENTAL_USE_REALFFTF #ifdef EXPERIMENTAL_USE_REALFFTF
mWindowType = -1; mWindowType = -1;
mWindowSize = -1; mWindowSize = -1;
hFFT = NULL; hFFT = NULL;
mWindow = NULL; mWindow = NULL;
#endif #endif
mSpecCache = new SpecCache(1, 1, false); mSpecCache = new SpecCache(0, 1, false);
mSpecPxCache = new SpecPxCache(1); mSpecPxCache = new SpecPxCache(1);
mAppendBuffer = NULL; mAppendBuffer = NULL;
mAppendBufferLen = 0; mAppendBufferLen = 0;
@ -325,14 +327,14 @@ WaveClip::WaveClip(const WaveClip& orig, DirManager *projDirManager)
mEnvelope->Paste(0.0, orig.mEnvelope); mEnvelope->Paste(0.0, orig.mEnvelope);
mEnvelope->SetOffset(orig.GetOffset()); mEnvelope->SetOffset(orig.GetOffset());
mEnvelope->SetTrackLen(((double)orig.mSequence->GetNumSamples()) / orig.mRate); mEnvelope->SetTrackLen(((double)orig.mSequence->GetNumSamples()) / orig.mRate);
mWaveCache = new WaveCache(1); mWaveCache = new WaveCache(0);
#ifdef EXPERIMENTAL_USE_REALFFTF #ifdef EXPERIMENTAL_USE_REALFFTF
mWindowType = -1; mWindowType = -1;
mWindowSize = -1; mWindowSize = -1;
hFFT = NULL; hFFT = NULL;
mWindow = NULL; mWindow = NULL;
#endif #endif
mSpecCache = new SpecCache(1, 1, false); mSpecCache = new SpecCache(0, 1, false);
mSpecPxCache = new SpecPxCache(1); mSpecPxCache = new SpecPxCache(1);
for (WaveClipList::compatibility_iterator it=orig.mCutLines.GetFirst(); it; it=it->GetNext()) for (WaveClipList::compatibility_iterator it=orig.mCutLines.GetFirst(); it; it=it->GetNext())
@ -439,7 +441,7 @@ void WaveClip::DeleteWaveCache()
mWaveCacheMutex.Lock(); mWaveCacheMutex.Lock();
if(mWaveCache!=NULL) if(mWaveCache!=NULL)
delete mWaveCache; delete mWaveCache;
mWaveCache = new WaveCache(1); mWaveCache = new WaveCache(0);
mWaveCacheMutex.Unlock(); mWaveCacheMutex.Unlock();
} }
@ -526,15 +528,43 @@ bool WaveClip::GetWaveDisplay(float *min, float *max, float *rms,int* bl,
mWaveCache->rate = mRate; mWaveCache->rate = mRate;
mWaveCache->start = t0; mWaveCache->start = t0;
double tstep = 1.0 / pixelsPerSecond; double tstep = 1.0 / pixelsPerSecond;
double samplesPerPixel = mRate * tstep;
sampleCount x; sampleCount oldWhere0 = 0;
sampleCount denom = 0;
int oldX0 = 0, oldXLast = 0;
double error = 0.0;
if (oldCache->len > 0) {
oldWhere0 = oldCache->where[1] - samplesPerPixel;
const sampleCount oldWhereLast(floor(0.5 +
oldWhere0 + oldCache->len * samplesPerPixel
));
denom = oldWhereLast - oldWhere0;
for (x = 0; x < mWaveCache->len + 1; x++) { // Mitigate the accumulation of location errors
mWaveCache->where[x] = // in copies of copies of ... of caches.
(sampleCount) floor(t0 * mRate + if (denom > 0)
((double) x) * mRate * tstep + 0.5); {
const double guessWhere0 = t0 * mRate;
oldX0 = floor(0.5 + oldCache->len * (guessWhere0 - oldWhere0) / denom);
const double where0 =
(sampleCount)floor(0.5 + oldWhere0 + double(oldX0) * samplesPerPixel);
error = where0 - guessWhere0; // should be in (-samplesPerPixel, samplesPerPixel)
wxASSERT(-samplesPerPixel <= error && error <= samplesPerPixel);
oldXLast = floor(0.5 + oldCache->len * (
(where0 + double(mWaveCache->len) * samplesPerPixel - oldWhere0)
/ denom
));
}
} }
// Be careful to make the first value non-negative
mWaveCache->where[0] = sampleCount(std::max(0.0, floor(0.5 + error + t0 * mRate)));
for (sampleCount x = 1; x < mWaveCache->len + 1; x++)
mWaveCache->where[x] = sampleCount(
floor(0.5 + error + t0 * mRate + double(x) * samplesPerPixel)
);
//mchinen: I think s0 - s1 represents the range of samples that we will need to look up. likewise p0-p1 the number of pixels. //mchinen: I think s0 - s1 represents the range of samples that we will need to look up. likewise p0-p1 the number of pixels.
sampleCount s0 = mWaveCache->where[0]; sampleCount s0 = mWaveCache->where[0];
sampleCount s1 = mWaveCache->where[mWaveCache->len]; sampleCount s1 = mWaveCache->where[mWaveCache->len];
@ -544,10 +574,11 @@ bool WaveClip::GetWaveDisplay(float *min, float *max, float *rms,int* bl,
// Optimization: if the old cache is good and overlaps // Optimization: if the old cache is good and overlaps
// with the current one, re-use as much of the cache as // with the current one, re-use as much of the cache as
// possible // possible
if (oldCache->dirty == mDirty && if (denom > 0 &&
oldCache->dirty == mDirty &&
oldCache->pps == pixelsPerSecond && oldCache->pps == pixelsPerSecond &&
oldCache->where[0] < mWaveCache->where[mWaveCache->len] && oldX0 < oldCache->len &&
oldCache->where[oldCache->len] > mWaveCache->where[0]) { oldXLast > oldCache->start) {
//now we are assuming the entire range is covered by the old cache and reducing s1/s0 as we find out otherwise. //now we are assuming the entire range is covered by the old cache and reducing s1/s0 as we find out otherwise.
s0 = mWaveCache->where[mWaveCache->len]; //mchinen:s0 is the min sample covered up to by the wave cache. will shrink if old doen't overlap s0 = mWaveCache->where[mWaveCache->len]; //mchinen:s0 is the min sample covered up to by the wave cache. will shrink if old doen't overlap
@ -576,23 +607,15 @@ bool WaveClip::GetWaveDisplay(float *min, float *max, float *rms,int* bl,
} }
oldCache->ClearInvalidRegions(); oldCache->ClearInvalidRegions();
for (x = 0; x < mWaveCache->len; x++) for (sampleCount x = 0; x < mWaveCache->len; x++)
{ {
//if we hit a cached column, load it up.
const double whereX = t0 * mRate + ((double)x) * samplesPerPixel;
const double oxd = (double(oldCache->len) * (whereX - oldWhere0)) / denom;
int ox = floor(0.5 + oxd);
//below is regular cache access. //below is regular cache access.
if (mWaveCache->where[x] >= oldCache->where[0] && if (ox >= 0 && ox < oldCache->len) {
mWaveCache->where[x] <= oldCache->where[oldCache->len - 1]) {
//if we hit an invalid region, load it up.
int ox =
int ((double (oldCache->len) *
(mWaveCache->where[x] -
oldCache->where[0])) /(oldCache->where[oldCache->len] -
oldCache->where[0]) + 0.5);
mWaveCache->min[x] = oldCache->min[ox]; mWaveCache->min[x] = oldCache->min[ox];
mWaveCache->max[x] = oldCache->max[ox]; mWaveCache->max[x] = oldCache->max[ox];
mWaveCache->rms[x] = oldCache->rms[ox]; mWaveCache->rms[x] = oldCache->rms[ox];
@ -795,22 +818,55 @@ bool WaveClip::GetSpectrogram(float *freq, sampleCount *where,
mSpecCache->pps = pixelsPerSecond; mSpecCache->pps = pixelsPerSecond;
mSpecCache->start = t0; mSpecCache->start = t0;
sampleCount x;
bool *recalc = new bool[mSpecCache->len + 1]; bool *recalc = new bool[mSpecCache->len + 1];
for (x = 0; x < mSpecCache->len + 1; x++) { const double tstep = 1.0 / pixelsPerSecond;
const double samplesPerPixel = mRate * tstep;
sampleCount oldWhere0 = 0;
sampleCount denom = 0;
int oldX0 = 0, oldXLast = 0;
double error = 0.0;
if (oldCache->len > 0) {
oldWhere0 = oldCache->where[1] - samplesPerPixel;
const sampleCount oldWhereLast(floor(0.5 +
oldWhere0 + oldCache->len * samplesPerPixel
));
denom = oldWhereLast - oldWhere0;
// Mitigate the accumulation of location errors
// in copies of copies of ... of caches.
if (denom > 0)
{
const double guessWhere0 = t0 * mRate;
oldX0 = floor(0.5 + oldCache->len * (guessWhere0 - oldWhere0) / denom);
const double where0 =
(sampleCount)floor(0.5 + oldWhere0 + double(oldX0) * samplesPerPixel);
error = where0 - guessWhere0; // should be in (-samplesPerPixel, samplesPerPixel)
oldXLast = floor(0.5 + oldCache->len * (
(where0 + double(mWaveCache->len) * samplesPerPixel - oldWhere0)
/ denom
));
}
}
// Be careful to make the first value non-negative
recalc[0] = true;
mSpecCache->where[0] = sampleCount(std::max(0.0, floor(1.0 + error + t0 * mRate)));
for (sampleCount x = 1; x < mSpecCache->len + 1; x++) {
recalc[x] = true; recalc[x] = true;
// purposely offset the display 1/2 bin to the left (as compared // purposely offset the display 1/2 bin to the left (as compared
// to waveform display to properly center response of the FFT // to waveform display to properly center response of the FFT
mSpecCache->where[x] = mSpecCache->where[x] =
(sampleCount)floor((t0*mRate) + (x*mRate/pixelsPerSecond) + 1.); sampleCount(floor(1.0 + error + t0 * mRate + double(x) * samplesPerPixel));
} }
// Optimization: if the old cache is good and overlaps // Optimization: if the old cache is good and overlaps
// with the current one, re-use as much of the cache as // with the current one, re-use as much of the cache as
// possible // possible
if (oldCache->dirty == mDirty && if (denom > 0 &&
oldCache->dirty == mDirty &&
oldCache->minFreqOld == minFreq && oldCache->minFreqOld == minFreq &&
oldCache->maxFreqOld == maxFreq && oldCache->maxFreqOld == maxFreq &&
oldCache->rangeOld == range && oldCache->rangeOld == range &&
@ -823,27 +879,24 @@ bool WaveClip::GetSpectrogram(float *freq, sampleCount *where,
#endif //EXPERIMENTAL_FFT_SKIP_POINTS #endif //EXPERIMENTAL_FFT_SKIP_POINTS
oldCache->pps == pixelsPerSecond && oldCache->pps == pixelsPerSecond &&
oldCache->ac == autocorrelation && oldCache->ac == autocorrelation &&
oldCache->where[0] < mSpecCache->where[mSpecCache->len] && oldX0 < oldCache->len &&
oldCache->where[oldCache->len] > mSpecCache->where[0]) { oldXLast > oldCache->start) {
for (sampleCount x = 0; x < mSpecCache->len; x++) {
for (x = 0; x < mSpecCache->len; x++) //if we hit a cached column, load it up.
if (mSpecCache->where[x] >= oldCache->where[0] && const double whereX = t0 * mRate + ((double)x) * samplesPerPixel;
mSpecCache->where[x] <= oldCache->where[oldCache->len]) { const double oxd = (double(oldCache->len) * (whereX - oldWhere0)) / denom;
int ox = floor(0.5 + oxd);
int ox = (int) ((double (oldCache->len) *
(mSpecCache->where[x] - oldCache->where[0]))
/ (oldCache->where[oldCache->len] -
oldCache->where[0]) + 0.5);
if (ox >= 0 && ox < oldCache->len &&
mSpecCache->where[x] == oldCache->where[ox]) {
//below is regular cache access.
if (ox >= 0 && ox < oldCache->len) {
if (mSpecCache->where[x] >= oldCache->where[0] &&
mSpecCache->where[x] <= oldCache->where[oldCache->len]) {
for (sampleCount i = 0; i < (sampleCount)half; i++) for (sampleCount i = 0; i < (sampleCount)half; i++)
mSpecCache->freq[half * x + i] = mSpecCache->freq[half * x + i] = oldCache->freq[half * ox + i];
oldCache->freq[half * ox + i];
recalc[x] = false; recalc[x] = false;
} }
} }
}
} }
#ifdef EXPERIMENTAL_FFT_SKIP_POINTS #ifdef EXPERIMENTAL_FFT_SKIP_POINTS
@ -866,12 +919,12 @@ bool WaveClip::GetSpectrogram(float *freq, sampleCount *where,
// scaled such that 1000 Hz gets a gain of 0dB // scaled such that 1000 Hz gets a gain of 0dB
double factor = 0.001*(double)mRate/(double)windowSize; double factor = 0.001*(double)mRate/(double)windowSize;
gainfactor = new float[half]; gainfactor = new float[half];
for(x = 0; x < half; x++) { for(sampleCount x = 0; x < half; x++) {
gainfactor[x] = frequencygain*log10(factor * x); gainfactor[x] = frequencygain*log10(factor * x);
} }
} }
for (x = 0; x < mSpecCache->len; x++) for (sampleCount x = 0; x < mSpecCache->len; x++)
if (recalc[x]) { if (recalc[x]) {
sampleCount start = mSpecCache->where[x]; sampleCount start = mSpecCache->where[x];
@ -1602,11 +1655,11 @@ bool WaveClip::Resample(int rate, ProgressDialog *progress)
delete mWaveCache; delete mWaveCache;
mWaveCache = NULL; mWaveCache = NULL;
} }
mWaveCache = new WaveCache(1); mWaveCache = new WaveCache(0);
// Invalidate the spectrum display cache // Invalidate the spectrum display cache
if (mSpecCache) if (mSpecCache)
delete mSpecCache; delete mSpecCache;
mSpecCache = new SpecCache(1, 1, false); mSpecCache = new SpecCache(0, 1, false);
} }
return !error; return !error;