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:
commit
c65570c465
167
src/WaveClip.cpp
167
src/WaveClip.cpp
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user