1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-24 08:10:05 +02:00

reassignment, internals, implementing frequency correction only

This commit is contained in:
Paul Licameli 2015-05-31 14:58:10 -04:00
parent 1f94d99c43
commit 7113d533fb
3 changed files with 172 additions and 53 deletions

View File

@ -792,6 +792,8 @@ void SpecCache::CalculateOneSpectrum
const std::vector<float> &gainFactors, const std::vector<float> &gainFactors,
float *scratch) float *scratch)
{ {
const bool reassignment =
(settings.algorithm == SpectrogramSettings::algReassignment);
const int windowSize = settings.windowSize; const int windowSize = settings.windowSize;
sampleCount start = where[xx]; sampleCount start = where[xx];
const bool autocorrelation = const bool autocorrelation =
@ -800,72 +802,142 @@ void SpecCache::CalculateOneSpectrum
const int padding = (windowSize * (zeroPaddingFactor - 1)) / 2; const int padding = (windowSize * (zeroPaddingFactor - 1)) / 2;
const int fftLen = windowSize * zeroPaddingFactor; const int fftLen = windowSize * zeroPaddingFactor;
const int half = fftLen / 2; const int half = fftLen / 2;
float *const results = &freq[half * xx];
sampleCount len = windowSize;
if (start <= 0 || start >= numSamples) { if (start <= 0 || start >= numSamples) {
// Pixel column is out of bounds of the clip! Should not happen. if (xx >= 0 && xx < len) {
std::fill(results, results + half, 0.0f); // Pixel column is out of bounds of the clip! Should not happen.
float *const results = &freq[half * xx];
std::fill(results, results + half, 0.0f);
}
} }
else { else {
bool copy = !autocorrelation || (padding > 0); // We can avoid copying memory when ComputeSpectrum is used below
bool copy = !autocorrelation || (padding > 0) || reassignment;
float *useBuffer = 0; float *useBuffer = 0;
float *adj = scratch + padding; float *adj = scratch + padding;
// Take a window of the track centered at this sample. {
start -= windowSize >> 1; sampleCount myLen = windowSize;
if (start < 0) { // Take a window of the track centered at this sample.
// Near the start of the clip, pad left with zeroes as needed. start -= windowSize >> 1;
for (sampleCount ii = start; ii < 0; ++ii) if (start < 0) {
*adj++ = 0; // Near the start of the clip, pad left with zeroes as needed.
len += start; for (sampleCount ii = start; ii < 0; ++ii)
start = 0; *adj++ = 0;
copy = true; myLen += start;
} start = 0;
if (start + len > numSamples) { copy = true;
// Near the end of the clip, pad right with zeroes as needed. }
int newlen = numSamples - start;
for (sampleCount ii = newlen; ii < (sampleCount)len; ++ii)
adj[ii] = 0;
len = newlen;
copy = true;
}
if (len > 0) { if (start + myLen > numSamples) {
// Copy samples out of the track. // Near the end of the clip, pad right with zeroes as needed.
useBuffer = (float*)(waveTrackCache.Get(floatSample, int newlen = numSamples - start;
floor(0.5 + start + offset * rate), len)); for (sampleCount ii = newlen; ii < (sampleCount)myLen; ++ii)
if (copy) adj[ii] = 0;
memcpy(adj, useBuffer, len * sizeof(float)); myLen = newlen;
copy = true;
}
if (myLen > 0) {
useBuffer = (float*)(waveTrackCache.Get(floatSample,
floor(0.5 + start + offset * rate), myLen));
if (copy)
memcpy(adj, useBuffer, myLen * sizeof(float));
}
} }
if (copy) if (copy)
useBuffer = scratch; useBuffer = scratch;
#ifdef EXPERIMENTAL_USE_REALFFTF #ifdef EXPERIMENTAL_USE_REALFFTF
if (autocorrelation) if (autocorrelation) {
float *const results = &freq[half * xx];
// This function does not mutate useBuffer
ComputeSpectrum(useBuffer, windowSize, windowSize, ComputeSpectrum(useBuffer, windowSize, windowSize,
rate, results, rate, results,
autocorrelation, settings.windowType); autocorrelation, settings.windowType);
else }
// Do the FFT. Note that scratch is multiplied by the window, else if (reassignment) {
static const double epsilon = 1e-16;
const HFFT hFFT = settings.hFFT;
float *const scratch2 = scratch + fftLen;
std::copy(scratch, scratch2, scratch2);
{
const float *const window = settings.window;
for (int ii = 0; ii < fftLen; ++ii)
scratch[ii] *= window[ii];
RealFFTf(scratch, hFFT);
}
{
const float *const dWindow = settings.dWindow;
for (int ii = 0; ii < fftLen; ++ii)
scratch2[ii] *= dWindow[ii];
RealFFTf(scratch2, hFFT);
}
const double multiplier = -fftLen / (2.0f * M_PI);
for (int ii = 0; ii < hFFT->Points; ++ii) {
const int index = hFFT->BitReversed[ii];
const float
denomRe = scratch[index],
denomIm = ii == 0 ? 0 : scratch[index + 1];
const double power = denomRe * denomRe + denomIm * denomIm;
if (power < epsilon)
// Avoid dividing by near-zero below
continue;
const float
numRe = scratch2[index],
numIm = ii == 0 ? 0 : scratch2[index + 1];
// Find complex quotient --
// Which means, multiply numerator by conjugate of denominator,
// then divide by norm squared of denominator --
// Then just take its imaginary part.
const double
quotIm = (-numRe * denomIm + numIm * denomRe) / power;
// With appropriate multiplier, that becomes the correction of
// the frequency bin.
const double correction = multiplier * quotIm;
const int bin = int(ii + correction + 0.5f);
if (bin >= 0 && bin < hFFT->Points)
freq[half * xx + bin] += power;
}
// Now Convert to dB terms
for (int ii = 0; ii < hFFT->Points; ++ii) {
float &power = freq[half * xx + ii];
if (power <= 0)
power = -160.0;
else
power = 10.0*log10f(power);
}
}
else {
float *const results = &freq[half * xx];
// Do the FFT. Note that useBuffer is multiplied by the window,
// and the window is initialized with leading and trailing zeroes // and the window is initialized with leading and trailing zeroes
// when there is padding. Therefore we did not need to reinitialize // when there is padding. Therefore we did not need to reinitialize
// the part of scratch in the padding zones. // the part of useBuffer in the padding zones.
// This function mutates useBuffer
ComputeSpectrumUsingRealFFTf ComputeSpectrumUsingRealFFTf
(useBuffer, settings.hFFT, settings.window, fftLen, results); (useBuffer, settings.hFFT, settings.window, fftLen, results);
if (!gainFactors.empty()) {
// Apply a frequency-dependant gain factor
for (int ii = 0; ii < half; ++ii)
results[ii] += gainFactors[ii];
}
}
#else // EXPERIMENTAL_USE_REALFFTF #else // EXPERIMENTAL_USE_REALFFTF
ComputeSpectrum(buffer, windowSize, windowSize, // This function does not mutate scratch
ComputeSpectrum(scratch, windowSize, windowSize,
rate, results, rate, results,
autocorrelation, settings.windowType); autocorrelation, settings.windowType);
#endif // EXPERIMENTAL_USE_REALFFTF #endif // EXPERIMENTAL_USE_REALFFTF
if (!autocorrelation &&
!gainFactors.empty()) {
// Apply a frequency-dependant gain factor
for (int ii = 0; ii < half; ++ii)
results[ii] += gainFactors[ii];
}
} }
} }
@ -883,6 +955,8 @@ void SpecCache::Populate
const int &windowSize = settings.windowSize; const int &windowSize = settings.windowSize;
const bool autocorrelation = const bool autocorrelation =
settings.algorithm == SpectrogramSettings::algPitchEAC; settings.algorithm == SpectrogramSettings::algPitchEAC;
const bool reassignment =
settings.algorithm == SpectrogramSettings::algReassignment;
#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
const int &zeroPaddingFactor = autocorrelation ? 1 : settings.zeroPaddingFactor; const int &zeroPaddingFactor = autocorrelation ? 1 : settings.zeroPaddingFactor;
#else #else
@ -892,13 +966,15 @@ void SpecCache::Populate
// FFT length may be longer than the window of samples that affect results // FFT length may be longer than the window of samples that affect results
// because of zero padding done for increased frequency resolution // because of zero padding done for increased frequency resolution
const int fftLen = windowSize * zeroPaddingFactor; const int fftLen = windowSize * zeroPaddingFactor;
const int half = fftLen / 2;
std::vector<float> buffer( const size_t bufferSize = fftLen;
fftLen
); std::vector<float> buffer(reassignment ? 2 * bufferSize : bufferSize);
std::vector<float> gainFactors; std::vector<float> gainFactors;
ComputeSpectrogramGainFactors(fftLen, rate, frequencyGain, gainFactors); if (!autocorrelation)
ComputeSpectrogramGainFactors(fftLen, rate, frequencyGain, gainFactors);
// Loop over the ranges before and after the copied portion and compute anew. // Loop over the ranges before and after the copied portion and compute anew.
// One of the ranges may be empty. // One of the ranges may be empty.
@ -909,6 +985,27 @@ void SpecCache::Populate
CalculateOneSpectrum( CalculateOneSpectrum(
settings, waveTrackCache, xx, numSamples, settings, waveTrackCache, xx, numSamples,
offset, rate, gainFactors, &buffer[0]); offset, rate, gainFactors, &buffer[0]);
if (reassignment) {
// Now Convert to dB terms. Do this only after accumulating
// power values, which may cross columns with the time correction.
for (sampleCount xx = lowerBoundX; xx < upperBoundX; ++xx) {
float *const results = &freq[half * xx];
const HFFT hFFT = settings.hFFT;
for (int ii = 0; ii < hFFT->Points; ++ii) {
float &power = results[ii];
if (power <= 0)
power = -160.0;
else
power = 10.0*log10f(power);
}
if (!gainFactors.empty()) {
// Apply a frequency-dependant gain factor
for (int ii = 0; ii < half; ++ii)
results[ii] += gainFactors[ii];
}
}
}
} }
} }
@ -979,6 +1076,8 @@ bool WaveClip::GetSpectrogram(WaveTrackCache &waveTrackCache,
numPixels, settings.algorithm, pixelsPerSecond, t0, numPixels, settings.algorithm, pixelsPerSecond, t0,
windowType, windowSize, zeroPaddingFactor, frequencyGain); windowType, windowSize, zeroPaddingFactor, frequencyGain);
// purposely offset the display 1/2 sample to the left (as compared
// to waveform display) to properly center response of the FFT
fillWhere(mSpecCache->where, numPixels, 0.5, correction, fillWhere(mSpecCache->where, numPixels, 0.5, correction,
t0, mRate, samplesPerPixel); t0, mRate, samplesPerPixel);

View File

@ -56,6 +56,7 @@ SpectrogramSettings::Globals
SpectrogramSettings::SpectrogramSettings() SpectrogramSettings::SpectrogramSettings()
: hFFT(0) : hFFT(0)
, window(0) , window(0)
, dWindow(0)
{ {
LoadPrefs(); LoadPrefs();
} }
@ -92,6 +93,10 @@ SpectrogramSettings::SpectrogramSettings(const SpectrogramSettings &other)
// Do not copy these! // Do not copy these!
, hFFT(0) , hFFT(0)
, window(0) , window(0)
#if 0
, tWindow(0)
#endif
, dWindow(0)
{ {
} }
@ -388,6 +393,10 @@ void SpectrogramSettings::DestroyWindows()
delete[] window; delete[] window;
window = NULL; window = NULL;
} }
if (dWindow != NULL) {
delete[] dWindow;
dWindow = NULL;
}
#endif #endif
} }
@ -405,7 +414,11 @@ namespace
window = new float[fftLen]; window = new float[fftLen];
int ii; int ii;
const bool extra = padding > 0;
wxASSERT(windowSize % 2 == 0); wxASSERT(windowSize % 2 == 0);
if (extra)
// For windows that do not go to 0 at the edges, this improves symmetry
++windowSize;
const int endOfWindow = padding + windowSize; const int endOfWindow = padding + windowSize;
// Left and right padding // Left and right padding
for (ii = 0; ii < padding; ++ii) { for (ii = 0; ii < padding; ++ii) {
@ -418,25 +431,19 @@ namespace
// Overwrite middle as needed // Overwrite middle as needed
switch (which) { switch (which) {
case WINDOW: case WINDOW:
WindowFunc(windowType, windowSize, window + padding); NewWindowFunc(windowType, windowSize, extra, window + padding);
// NewWindowFunc(windowType, windowSize, extra, window + padding);
break; break;
case TWINDOW:
wxASSERT(false);
#if 0 #if 0
// Future, reassignment // Future, reassignment
case TWINDOW:
NewWindowFunc(windowType, windowSize, extra, window + padding); NewWindowFunc(windowType, windowSize, extra, window + padding);
for (int ii = padding, multiplier = -windowSize / 2; ii < endOfWindow; ++ii, ++multiplier) for (int ii = padding, multiplier = -windowSize / 2; ii < endOfWindow; ++ii, ++multiplier)
window[ii] *= multiplier; window[ii] *= multiplier;
break; break;
#endif #endif
case DWINDOW: case DWINDOW:
wxASSERT(false);
#if 0
// Future, reassignment
DerivativeOfWindowFunc(windowType, windowSize, extra, window + padding); DerivativeOfWindowFunc(windowType, windowSize, extra, window + padding);
break; break;
#endif
default: default:
wxASSERT(false); wxASSERT(false);
} }
@ -466,6 +473,12 @@ void SpectrogramSettings::CacheWindows() const
EndFFT(hFFT); EndFFT(hFFT);
hFFT = InitializeFFT(fftLen); hFFT = InitializeFFT(fftLen);
RecreateWindow(window, WINDOW, fftLen, padding, windowType, windowSize, scale); RecreateWindow(window, WINDOW, fftLen, padding, windowType, windowSize, scale);
if (algorithm == algReassignment) {
#if 0
RecreateWindow(tWindow, TWINDOW, fftLen, padding, windowType, windowSize, scale);
#endif
RecreateWindow(dWindow, DWINDOW, fftLen, padding, windowType, windowSize, scale);
}
} }
#endif // EXPERIMENTAL_USE_REALFFTF #endif // EXPERIMENTAL_USE_REALFFTF
} }

View File

@ -154,6 +154,13 @@ public:
// Variables used for computing the spectrum // Variables used for computing the spectrum
mutable FFTParam *hFFT; mutable FFTParam *hFFT;
mutable float *window; mutable float *window;
// Two other windows for computing reassigned spectrogram
#if 0
mutable float *tWindow; // Window times time parameter
#endif
mutable float *dWindow; // Derivative of window
#endif #endif
}; };
#endif #endif