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:
commit
c65570c465
163
src/WaveClip.cpp
163
src/WaveClip.cpp
@ -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,14 +528,42 @@ 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];
|
||||||
@ -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,28 +879,25 @@ 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++) {
|
||||||
|
//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);
|
||||||
|
|
||||||
for (x = 0; x < mSpecCache->len; x++)
|
//below is regular cache access.
|
||||||
|
if (ox >= 0 && ox < oldCache->len) {
|
||||||
if (mSpecCache->where[x] >= oldCache->where[0] &&
|
if (mSpecCache->where[x] >= oldCache->where[0] &&
|
||||||
mSpecCache->where[x] <= oldCache->where[oldCache->len]) {
|
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]) {
|
|
||||||
|
|
||||||
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
|
||||||
float *buffer = new float[windowSize*fftSkipPoints1];
|
float *buffer = new float[windowSize*fftSkipPoints1];
|
||||||
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user