1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-03 17:19:43 +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;
pps = 0.0;
len = cacheLen;
min = new float[len];
max = new float[len];
rms = new float[len];
bl = new int[len];
min = len ? new float[len] : 0;
max = len ? new float[len] : 0;
rms = len ? new float[len] : 0;
bl = len ? new int[len] : 0;
where = new sampleCount[len+1];
where[0] = 0;
numODPixels=0;
}
@ -233,8 +234,9 @@ public:
pps = 0.0;
len = cacheLen;
ac = autocorrelation;
freq = new float[len*half];
freq = len ? new float[len*half] : 0;
where = new sampleCount[len+1];
where[0] = 0;
}
~SpecCache()
@ -297,14 +299,14 @@ WaveClip::WaveClip(DirManager *projDirManager, sampleFormat format, int rate)
mRate = rate;
mSequence = new Sequence(projDirManager, format);
mEnvelope = new Envelope();
mWaveCache = new WaveCache(1);
mWaveCache = new WaveCache(0);
#ifdef EXPERIMENTAL_USE_REALFFTF
mWindowType = -1;
mWindowSize = -1;
hFFT = NULL;
mWindow = NULL;
#endif
mSpecCache = new SpecCache(1, 1, false);
mSpecCache = new SpecCache(0, 1, false);
mSpecPxCache = new SpecPxCache(1);
mAppendBuffer = NULL;
mAppendBufferLen = 0;
@ -325,14 +327,14 @@ WaveClip::WaveClip(const WaveClip& orig, DirManager *projDirManager)
mEnvelope->Paste(0.0, orig.mEnvelope);
mEnvelope->SetOffset(orig.GetOffset());
mEnvelope->SetTrackLen(((double)orig.mSequence->GetNumSamples()) / orig.mRate);
mWaveCache = new WaveCache(1);
mWaveCache = new WaveCache(0);
#ifdef EXPERIMENTAL_USE_REALFFTF
mWindowType = -1;
mWindowSize = -1;
hFFT = NULL;
mWindow = NULL;
#endif
mSpecCache = new SpecCache(1, 1, false);
mSpecCache = new SpecCache(0, 1, false);
mSpecPxCache = new SpecPxCache(1);
for (WaveClipList::compatibility_iterator it=orig.mCutLines.GetFirst(); it; it=it->GetNext())
@ -439,7 +441,7 @@ void WaveClip::DeleteWaveCache()
mWaveCacheMutex.Lock();
if(mWaveCache!=NULL)
delete mWaveCache;
mWaveCache = new WaveCache(1);
mWaveCache = new WaveCache(0);
mWaveCacheMutex.Unlock();
}
@ -526,15 +528,43 @@ bool WaveClip::GetWaveDisplay(float *min, float *max, float *rms,int* bl,
mWaveCache->rate = mRate;
mWaveCache->start = t0;
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++) {
mWaveCache->where[x] =
(sampleCount) floor(t0 * mRate +
((double) x) * mRate * tstep + 0.5);
// 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)
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.
sampleCount s0 = mWaveCache->where[0];
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
// with the current one, re-use as much of the cache as
// possible
if (oldCache->dirty == mDirty &&
if (denom > 0 &&
oldCache->dirty == mDirty &&
oldCache->pps == pixelsPerSecond &&
oldCache->where[0] < mWaveCache->where[mWaveCache->len] &&
oldCache->where[oldCache->len] > mWaveCache->where[0]) {
oldX0 < oldCache->len &&
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.
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();
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.
if (mWaveCache->where[x] >= oldCache->where[0] &&
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);
if (ox >= 0 && ox < oldCache->len) {
mWaveCache->min[x] = oldCache->min[ox];
mWaveCache->max[x] = oldCache->max[ox];
mWaveCache->rms[x] = oldCache->rms[ox];
@ -795,22 +818,55 @@ bool WaveClip::GetSpectrogram(float *freq, sampleCount *where,
mSpecCache->pps = pixelsPerSecond;
mSpecCache->start = t0;
sampleCount x;
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;
// purposely offset the display 1/2 bin to the left (as compared
// to waveform display to properly center response of the FFT
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
// with the current one, re-use as much of the cache as
// possible
if (oldCache->dirty == mDirty &&
if (denom > 0 &&
oldCache->dirty == mDirty &&
oldCache->minFreqOld == minFreq &&
oldCache->maxFreqOld == maxFreq &&
oldCache->rangeOld == range &&
@ -823,27 +879,24 @@ bool WaveClip::GetSpectrogram(float *freq, sampleCount *where,
#endif //EXPERIMENTAL_FFT_SKIP_POINTS
oldCache->pps == pixelsPerSecond &&
oldCache->ac == autocorrelation &&
oldCache->where[0] < mSpecCache->where[mSpecCache->len] &&
oldCache->where[oldCache->len] > mSpecCache->where[0]) {
for (x = 0; x < mSpecCache->len; x++)
if (mSpecCache->where[x] >= oldCache->where[0] &&
mSpecCache->where[x] <= oldCache->where[oldCache->len]) {
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]) {
oldX0 < oldCache->len &&
oldXLast > oldCache->start) {
for (sampleCount x = 0; x < mSpecCache->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.
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++)
mSpecCache->freq[half * x + i] =
oldCache->freq[half * ox + i];
mSpecCache->freq[half * x + i] = oldCache->freq[half * ox + i];
recalc[x] = false;
}
}
}
}
#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
double factor = 0.001*(double)mRate/(double)windowSize;
gainfactor = new float[half];
for(x = 0; x < half; x++) {
for(sampleCount x = 0; x < half; 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]) {
sampleCount start = mSpecCache->where[x];
@ -1602,11 +1655,11 @@ bool WaveClip::Resample(int rate, ProgressDialog *progress)
delete mWaveCache;
mWaveCache = NULL;
}
mWaveCache = new WaveCache(1);
mWaveCache = new WaveCache(0);
// Invalidate the spectrum display cache
if (mSpecCache)
delete mSpecCache;
mSpecCache = new SpecCache(1, 1, false);
mSpecCache = new SpecCache(0, 1, false);
}
return !error;