Audacity 3.2.0
SpectrumCache.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file SpectrumCache.cpp
6
7 Paul Licameli split from WaveClip.cpp
8
9**********************************************************************/
10
11#include "SpectrumCache.h"
12
13#include <cmath>
14#include "RealFFTf.h"
15#include "SampleTrackCache.h"
16#include "../../../../prefs/SpectrogramSettings.h"
17#include "Spectrum.h"
18#include "WaveClipUtilities.h"
19#include "WaveTrack.h"
20
21class WaveTrack;
22
23namespace {
24
26 (float * __restrict buffer, const FFTParam *hFFT,
27 const float * __restrict window, size_t len, float * __restrict out)
28{
29 size_t i;
30 if(len > hFFT->Points * 2)
31 len = hFFT->Points * 2;
32 for(i = 0; i < len; i++)
33 buffer[i] *= window[i];
34 for( ; i < (hFFT->Points * 2); i++)
35 buffer[i] = 0; // zero pad as needed
36 RealFFTf(buffer, hFFT);
37 // Handle the (real-only) DC
38 float power = buffer[0] * buffer[0];
39 if(power <= 0)
40 out[0] = -160.0;
41 else
42 out[0] = 10.0 * log10f(power);
43 for(i = 1; i < hFFT->Points; i++) {
44 const int index = hFFT->BitReversed[i];
45 const float re = buffer[index], im = buffer[index + 1];
46 power = re * re + im * im;
47 if(power <= 0)
48 out[i] = -160.0;
49 else
50 out[i] = 10.0*log10f(power);
51 }
52}
53
55 (size_t fftLen, double rate, int frequencyGain, std::vector<float> &gainFactors)
56{
57 if (frequencyGain > 0) {
58 // Compute a frequency-dependent gain factor
59 // scaled such that 1000 Hz gets a gain of 0dB
60
61 // This is the reciprocal of the bin number of 1000 Hz:
62 const double factor = ((double)rate / (double)fftLen) / 1000.0;
63
64 auto half = fftLen / 2;
65 gainFactors.reserve(half);
66 // Don't take logarithm of zero! Let bin 0 replicate the gain factor for bin 1.
67 gainFactors.push_back(frequencyGain*log10(factor));
68 for (decltype(half) x = 1; x < half; x++) {
69 gainFactors.push_back(frequencyGain*log10(factor * x));
70 }
71 }
72}
73
74}
75
77 (int dirty_, double pixelsPerSecond,
78 const SpectrogramSettings &settings, double rate) const
79{
80 // Make a tolerant comparison of the pps values in this wise:
81 // accumulated difference of times over the number of pixels is less than
82 // a sample period.
83 const double tstep = 1.0 / pixelsPerSecond;
84 const bool ppsMatch =
85 (fabs(tstep - 1.0 / pps) * len < (1.0 / rate));
86
87 return
88 ppsMatch &&
89 dirty == dirty_ &&
90 windowType == settings.windowType &&
91 windowSize == settings.WindowSize() &&
92 zeroPaddingFactor == settings.ZeroPaddingFactor() &&
93 frequencyGain == settings.frequencyGain &&
94 algorithm == settings.algorithm;
95}
96
99 SampleTrackCache &waveTrackCache,
100 const int xx, const sampleCount numSamples,
101 double offset, double rate, double pixelsPerSecond,
102 int lowerBoundX, int upperBoundX,
103 const std::vector<float> &gainFactors,
104 float* __restrict scratch, float* __restrict out) const
105{
106 bool result = false;
107 const bool reassignment =
109 const size_t windowSizeSetting = settings.WindowSize();
110
111 sampleCount from;
112
113 // xx may be for a column that is out of the visible bounds, but only
114 // when we are calculating reassignment contributions that may cross into
115 // the visible area.
116
117 if (xx < 0)
118 from = sampleCount(
119 where[0].as_double() + xx * (rate / pixelsPerSecond)
120 );
121 else if (xx > (int)len)
122 from = sampleCount(
123 where[len].as_double() + (xx - len) * (rate / pixelsPerSecond)
124 );
125 else
126 from = where[xx];
127
128 const bool autocorrelation =
130 const size_t zeroPaddingFactorSetting = settings.ZeroPaddingFactor();
131 const size_t padding = (windowSizeSetting * (zeroPaddingFactorSetting - 1)) / 2;
132 const size_t fftLen = windowSizeSetting * zeroPaddingFactorSetting;
133 auto nBins = settings.NBins();
134
135 if (from < 0 || from >= numSamples) {
136 if (xx >= 0 && xx < (int)len) {
137 // Pixel column is out of bounds of the clip! Should not happen.
138 float *const results = &out[nBins * xx];
139 std::fill(results, results + nBins, 0.0f);
140 }
141 }
142 else {
143
144
145 // We can avoid copying memory when ComputeSpectrum is used below
146 bool copy = !autocorrelation || (padding > 0) || reassignment;
147 float *useBuffer = 0;
148 float *adj = scratch + padding;
149
150 {
151 auto myLen = windowSizeSetting;
152 // Take a window of the track centered at this sample.
153 from -= windowSizeSetting >> 1;
154 if (from < 0) {
155 // Near the start of the clip, pad left with zeroes as needed.
156 // from is at least -windowSize / 2
157 for (auto ii = from; ii < 0; ++ii)
158 *adj++ = 0;
159 myLen += from.as_long_long(); // add a negative
160 from = 0;
161 copy = true;
162 }
163
164 if (from + myLen >= numSamples) {
165 // Near the end of the clip, pad right with zeroes as needed.
166 // newlen is bounded by myLen:
167 auto newlen = ( numSamples - from ).as_size_t();
168 for (decltype(myLen) ii = newlen; ii < myLen; ++ii)
169 adj[ii] = 0;
170 myLen = newlen;
171 copy = true;
172 }
173
174 if (myLen > 0) {
175 useBuffer = (float*)(waveTrackCache.GetFloats(
177 floor(0.5 + from.as_double() + offset * rate)
178 ),
179 myLen,
180 // Don't throw in this drawing operation
181 false)
182 );
183
184 if (copy) {
185 if (useBuffer)
186 memcpy(adj, useBuffer, myLen * sizeof(float));
187 else
188 memset(adj, 0, myLen * sizeof(float));
189 }
190 }
191 }
192
193 if (copy || !useBuffer)
194 useBuffer = scratch;
195
196 if (autocorrelation) {
197 // not reassignment, xx is surely within bounds.
198 wxASSERT(xx >= 0);
199 float *const results = &out[nBins * xx];
200 // This function does not mutate useBuffer
201 ComputeSpectrum(useBuffer, windowSizeSetting, windowSizeSetting,
202 rate, results,
203 autocorrelation, settings.windowType);
204 }
205 else if (reassignment) {
206 static const double epsilon = 1e-16;
207 const auto hFFT = settings.hFFT.get();
208
209 float *const scratch2 = scratch + fftLen;
210 std::copy(scratch, scratch2, scratch2);
211
212 float *const scratch3 = scratch + 2 * fftLen;
213 std::copy(scratch, scratch2, scratch3);
214
215 {
216 const float *const window = settings.window.get();
217 for (size_t ii = 0; ii < fftLen; ++ii)
218 scratch[ii] *= window[ii];
219 RealFFTf(scratch, hFFT);
220 }
221
222 {
223 const float *const dWindow = settings.dWindow.get();
224 for (size_t ii = 0; ii < fftLen; ++ii)
225 scratch2[ii] *= dWindow[ii];
226 RealFFTf(scratch2, hFFT);
227 }
228
229 {
230 const float *const tWindow = settings.tWindow.get();
231 for (size_t ii = 0; ii < fftLen; ++ii)
232 scratch3[ii] *= tWindow[ii];
233 RealFFTf(scratch3, hFFT);
234 }
235
236 for (size_t ii = 0; ii < hFFT->Points; ++ii) {
237 const int index = hFFT->BitReversed[ii];
238 const float
239 denomRe = scratch[index],
240 denomIm = ii == 0 ? 0 : scratch[index + 1];
241 const double power = denomRe * denomRe + denomIm * denomIm;
242 if (power < epsilon)
243 // Avoid dividing by near-zero below
244 continue;
245
246 double freqCorrection;
247 {
248 const double multiplier = -(fftLen / (2.0f * M_PI));
249 const float
250 numRe = scratch2[index],
251 numIm = ii == 0 ? 0 : scratch2[index + 1];
252 // Find complex quotient --
253 // Which means, multiply numerator by conjugate of denominator,
254 // then divide by norm squared of denominator --
255 // Then just take its imaginary part.
256 const double
257 quotIm = (-numRe * denomIm + numIm * denomRe) / power;
258 // With appropriate multiplier, that becomes the correction of
259 // the frequency bin.
260 freqCorrection = multiplier * quotIm;
261 }
262
263 const int bin = (int)((int)ii + freqCorrection + 0.5f);
264 // Must check if correction takes bin out of bounds, above or below!
265 // bin is signed!
266 if (bin >= 0 && bin < (int)hFFT->Points) {
267 double timeCorrection;
268 {
269 const float
270 numRe = scratch3[index],
271 numIm = ii == 0 ? 0 : scratch3[index + 1];
272 // Find another complex quotient --
273 // Then just take its real part.
274 // The result has sample interval as unit.
275 timeCorrection =
276 (numRe * denomRe + numIm * denomIm) / power;
277 }
278
279 int correctedX = (floor(0.5 + xx + timeCorrection * pixelsPerSecond / rate));
280 if (correctedX >= lowerBoundX && correctedX < upperBoundX)
281 {
282 result = true;
283
284 // This is non-negative, because bin and correctedX are
285 auto ind = (int)nBins * correctedX + bin;
286#ifdef _OPENMP
287 // This assignment can race if index reaches into another thread's bins.
288 // The probability of a race very low, so this carries little overhead,
289 // about 5% slower vs allowing it to race.
290 #pragma omp atomic update
291#endif
292 out[ind] += power;
293 }
294 }
295 }
296 }
297 else {
298 // not reassignment, xx is surely within bounds.
299 wxASSERT(xx >= 0);
300 float *const results = &out[nBins * xx];
301
302 // Do the FFT. Note that useBuffer is multiplied by the window,
303 // and the window is initialized with leading and trailing zeroes
304 // when there is padding. Therefore we did not need to reinitialize
305 // the part of useBuffer in the padding zones.
306
307 // This function mutates useBuffer
309 (useBuffer, settings.hFFT.get(), settings.window.get(), fftLen, results);
310 if (!gainFactors.empty()) {
311 // Apply a frequency-dependent gain factor
312 for (size_t ii = 0; ii < nBins; ++ii)
313 results[ii] += gainFactors[ii];
314 }
315 }
316 }
317
318 return result;
319}
320
322 double pixelsPerSecond, double start_)
323{
324 settings.CacheWindows();
325
326 // len columns, and so many rows, column-major.
327 // Don't take column literally -- this isn't pixel data yet, it's the
328 // raw data to be mapped onto the display.
329 freq.resize(len_ * settings.NBins());
330
331 // Sample counts corresponding to the columns, and to one past the end.
332 where.resize(len_ + 1);
333
334 len = len_;
335 algorithm = settings.algorithm;
336 pps = pixelsPerSecond;
337 start = start_;
338 windowType = settings.windowType;
339 windowSize = settings.WindowSize();
340 zeroPaddingFactor = settings.ZeroPaddingFactor();
341 frequencyGain = settings.frequencyGain;
342}
343
345 (const SpectrogramSettings &settings, SampleTrackCache &waveTrackCache,
346 int copyBegin, int copyEnd, size_t numPixels,
347 sampleCount numSamples,
348 double offset, double rate, double pixelsPerSecond)
349{
350 const int &frequencyGainSetting = settings.frequencyGain;
351 const size_t windowSizeSetting = settings.WindowSize();
352 const bool autocorrelation =
354 const bool reassignment =
356 const size_t zeroPaddingFactorSetting = settings.ZeroPaddingFactor();
357
358 // FFT length may be longer than the window of samples that affect results
359 // because of zero padding done for increased frequency resolution
360 const size_t fftLen = windowSizeSetting * zeroPaddingFactorSetting;
361 const auto nBins = settings.NBins();
362
363 const size_t bufferSize = fftLen;
364 const size_t scratchSize = reassignment ? 3 * bufferSize : bufferSize;
365 std::vector<float> scratch(scratchSize);
366
367 std::vector<float> gainFactors;
368 if (!autocorrelation)
369 ComputeSpectrogramGainFactors(fftLen, rate, frequencyGainSetting, gainFactors);
370
371 // Loop over the ranges before and after the copied portion and compute anew.
372 // One of the ranges may be empty.
373 for (int jj = 0; jj < 2; ++jj) {
374 const int lowerBoundX = jj == 0 ? 0 : copyEnd;
375 const int upperBoundX = jj == 0 ? copyBegin : numPixels;
376
377#ifdef _OPENMP
378 // Storage for mutable per-thread data.
379 // private clause ensures one copy per thread
380 struct ThreadLocalStorage {
381 ThreadLocalStorage() { }
382 ~ThreadLocalStorage() { }
383
384 void init(SampleTrackCache &waveTrackCache, size_t scratchSize) {
385 if (!cache) {
386 cache = std::make_unique<SampleTrackCache>(waveTrackCache.GetTrack());
387 scratch.resize(scratchSize);
388 }
389 }
390 std::unique_ptr<SampleTrackCache> cache;
391 std::vector<float> scratch;
392 } tls;
393
394 #pragma omp parallel for private(tls)
395#endif
396 for (auto xx = lowerBoundX; xx < upperBoundX; ++xx)
397 {
398#ifdef _OPENMP
399 tls.init(waveTrackCache, scratchSize);
400 SampleTrackCache& cache = *tls.cache;
401 float* buffer = &tls.scratch[0];
402#else
403 SampleTrackCache& cache = waveTrackCache;
404 float* buffer = &scratch[0];
405#endif
407 settings, cache, xx, numSamples,
408 offset, rate, pixelsPerSecond,
409 lowerBoundX, upperBoundX,
410 gainFactors, buffer, &freq[0]);
411 }
412
413 if (reassignment) {
414 // Need to look beyond the edges of the range to accumulate more
415 // time reassignments.
416 // I'm not sure what's a good stopping criterion?
417 auto xx = lowerBoundX;
418 const double pixelsPerSample = pixelsPerSecond / rate;
419 const int limit = std::min((int)(0.5 + fftLen * pixelsPerSample), 100);
420 for (int ii = 0; ii < limit; ++ii)
421 {
422 const bool result =
424 settings, waveTrackCache, --xx, numSamples,
425 offset, rate, pixelsPerSecond,
426 lowerBoundX, upperBoundX,
427 gainFactors, &scratch[0], &freq[0]);
428 if (!result)
429 break;
430 }
431
432 xx = upperBoundX;
433 for (int ii = 0; ii < limit; ++ii)
434 {
435 const bool result =
437 settings, waveTrackCache, xx++, numSamples,
438 offset, rate, pixelsPerSecond,
439 lowerBoundX, upperBoundX,
440 gainFactors, &scratch[0], &freq[0]);
441 if (!result)
442 break;
443 }
444
445 // Now Convert to dB terms. Do this only after accumulating
446 // power values, which may cross columns with the time correction.
447#ifdef _OPENMP
448 #pragma omp parallel for
449#endif
450 for (xx = lowerBoundX; xx < upperBoundX; ++xx) {
451 float *const results = &freq[nBins * xx];
452 for (size_t ii = 0; ii < nBins; ++ii) {
453 float &power = results[ii];
454 if (power <= 0)
455 power = -160.0;
456 else
457 power = 10.0*log10f(power);
458 }
459 if (!gainFactors.empty()) {
460 // Apply a frequency-dependent gain factor
461 for (size_t ii = 0; ii < nBins; ++ii)
462 results[ii] += gainFactors[ii];
463 }
464 }
465 }
466 }
467}
468
470 SampleTrackCache &waveTrackCache,
471 const float *& spectrogram,
472 const sampleCount *& where,
473 size_t numPixels,
474 double t0, double pixelsPerSecond)
475{
476 t0 += clip.GetTrimLeft();
477
478 const auto track =
479 static_cast<const WaveTrack*>(waveTrackCache.GetTrack().get());
480 auto &settings = SpectrogramSettings::Get(*track);
481 const auto rate = clip.GetRate();
482
483 //Trim offset comparison failure forces spectrogram cache rebuild
484 //and skip copying "unchanged" data after clip border was trimmed.
485 bool match =
486 mSpecCache &&
487 mSpecCache->leftTrim == clip.GetTrimLeft() &&
488 mSpecCache->rightTrim == clip.GetTrimRight() &&
489 mSpecCache->len > 0 &&
490 mSpecCache->Matches
491 (mDirty, pixelsPerSecond, settings, rate);
492
493 if (match &&
494 mSpecCache->start == t0 &&
495 mSpecCache->len >= numPixels) {
496 spectrogram = &mSpecCache->freq[0];
497 where = &mSpecCache->where[0];
498
499 return false; //hit cache completely
500 }
501
502 // Caching is not implemented for reassignment, unless for
503 // a complete hit, because of the complications of time reassignment
505 match = false;
506
507 // Free the cache when it won't cause a major stutter.
508 // If the window size changed, we know there is nothing to be copied
509 // If we zoomed out, or resized, we can give up memory. But not too much -
510 // up to 2x extra is needed at the end of the clip to prevent stutter.
511 if (mSpecCache->freq.capacity() > 2.1 * mSpecCache->freq.size() ||
512 mSpecCache->windowSize*mSpecCache->zeroPaddingFactor <
513 settings.WindowSize()*settings.ZeroPaddingFactor())
514 {
515 match = false;
516 mSpecCache = std::make_unique<SpecCache>();
517 }
518
519 const double tstep = 1.0 / pixelsPerSecond;
520 const double samplesPerPixel = rate * tstep;
521
522 int oldX0 = 0;
523 double correction = 0.0;
524
525 int copyBegin = 0, copyEnd = 0;
526 if (match) {
527 findCorrection(mSpecCache->where, mSpecCache->len, numPixels,
528 t0, rate, samplesPerPixel,
529 oldX0, correction);
530 // Remember our first pixel maps to oldX0 in the old cache,
531 // possibly out of bounds.
532 // For what range of pixels can data be copied?
533 copyBegin = std::min((int)numPixels, std::max(0, -oldX0));
534 copyEnd = std::min((int)numPixels, std::max(0,
535 (int)mSpecCache->len - oldX0
536 ));
537 }
538
539 // Resize the cache, keep the contents unchanged.
540 mSpecCache->Grow(numPixels, settings, pixelsPerSecond, t0);
541 mSpecCache->leftTrim = clip.GetTrimLeft();
542 mSpecCache->rightTrim = clip.GetTrimRight();
543 auto nBins = settings.NBins();
544
545 // Optimization: if the old cache is good and overlaps
546 // with the current one, re-use as much of the cache as
547 // possible
548 if (copyEnd > copyBegin)
549 {
550 // memmove is required since dst/src overlap
551 memmove(&mSpecCache->freq[nBins * copyBegin],
552 &mSpecCache->freq[nBins * (copyBegin + oldX0)],
553 nBins * (copyEnd - copyBegin) * sizeof(float));
554 }
555
556 // Reassignment accumulates, so it needs a zeroed buffer
558 {
559 // The cache could theoretically copy from the middle, resulting
560 // in two regions to update. This won't happen in zoom, since
561 // old cache doesn't match. It won't happen in resize, since the
562 // spectrum view is pinned to left side of window.
563 wxASSERT(
564 (copyBegin >= 0 && copyEnd == (int)numPixels) || // copied the end
565 (copyBegin == 0 && copyEnd <= (int)numPixels) // copied the beginning
566 );
567
568 int zeroBegin = copyBegin > 0 ? 0 : copyEnd-copyBegin;
569 int zeroEnd = copyBegin > 0 ? copyBegin : numPixels;
570
571 memset(&mSpecCache->freq[nBins*zeroBegin], 0, nBins*(zeroEnd-zeroBegin)*sizeof(float));
572 }
573
574 // purposely offset the display 1/2 sample to the left (as compared
575 // to waveform display) to properly center response of the FFT
576 fillWhere(mSpecCache->where, numPixels, 0.5, correction,
577 t0, rate, samplesPerPixel);
578
579 mSpecCache->Populate
580 (settings, waveTrackCache, copyBegin, copyEnd, numPixels,
582 clip.GetSequenceStartTime(), rate, pixelsPerSecond);
583
584 mSpecCache->dirty = mDirty;
585 spectrogram = &mSpecCache->freq[0];
586 where = &mSpecCache->where[0];
587
588 return true;
589}
590
592: mSpecCache{ std::make_unique<SpecCache>() }
593, mSpecPxCache{ std::make_unique<SpecPxCache>(1) }
594{
595}
596
598{
599}
600
601static WaveClip::Caches::RegisteredFactory sKeyS{ []( WaveClip& ){
602 return std::make_unique< WaveClipSpectrumCache >();
603} };
604
606{
607 return const_cast< WaveClip& >( clip ) // Consider it mutable data
608 .Caches::Get< WaveClipSpectrumCache >( sKeyS );
609}
610
612{
613 ++mDirty;
614}
615
617{
618 // Invalidate the spectrum display cache
619 mSpecCache = std::make_unique<SpecCache>();
620}
int min(int a, int b)
#define M_PI
Definition: Distortion.cpp:30
void RealFFTf(fft_type *buffer, const FFTParam *h)
Definition: RealFFTf.cpp:161
bool ComputeSpectrum(const float *data, size_t width, size_t windowSize, double WXUNUSED(rate), float *output, bool autocorrelation, int windowFunc)
Definition: Spectrum.cpp:22
static WaveClip::Caches::RegisteredFactory sKeyS
static Settings & settings()
Definition: TrackInfo.cpp:87
void findCorrection(const std::vector< sampleCount > &oldWhere, size_t oldLen, size_t newLen, double t0, double rate, double samplesPerPixel, int &oldX0, double &correction)
void fillWhere(std::vector< sampleCount > &where, size_t len, double bias, double correction, double t0, double rate, double samplesPerPixel)
A short-lived object, during whose lifetime, the contents of the WaveTrack are assumed not to change.
const float * GetFloats(sampleCount start, size_t len, bool mayThrow)
Retrieve samples as floats from the track or from the memory cache.
const std::shared_ptr< const SampleTrack > & GetTrack() const
int windowType
Definition: SpectrumCache.h:73
unsigned zeroPaddingFactor
Definition: SpectrumCache.h:75
void Populate(const SpectrogramSettings &settings, SampleTrackCache &waveTrackCache, int copyBegin, int copyEnd, size_t numPixels, sampleCount numSamples, double offset, double rate, double pixelsPerSecond)
bool CalculateOneSpectrum(const SpectrogramSettings &settings, SampleTrackCache &waveTrackCache, const int xx, sampleCount numSamples, double offset, double rate, double pixelsPerSecond, int lowerBoundX, int upperBoundX, const std::vector< float > &gainFactors, float *__restrict scratch, float *__restrict out) const
size_t len
Definition: SpectrumCache.h:67
bool Matches(int dirty_, double pixelsPerSecond, const SpectrogramSettings &settings, double rate) const
double start
Definition: SpectrumCache.h:72
void Grow(size_t len_, SpectrogramSettings &settings, double pixelsPerSecond, double start_)
std::vector< float > freq
Definition: SpectrumCache.h:77
int frequencyGain
Definition: SpectrumCache.h:76
std::vector< sampleCount > where
Definition: SpectrumCache.h:78
double pps
Definition: SpectrumCache.h:69
size_t windowSize
Definition: SpectrumCache.h:74
Spectrogram settings, either for one track or as defaults.
static SpectrogramSettings & Get(const WaveTrack &track)
Mutative access to attachment even if the track argument is const.
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:101
double GetSequenceStartTime() const noexcept
Definition: WaveClip.cpp:956
double GetTrimRight() const noexcept
Returns the play end offset in seconds from the ending of the underlying sequence.
Definition: WaveClip.cpp:931
double GetTrimLeft() const noexcept
Returns the play start offset in seconds from the beginning of the underlying sequence.
Definition: WaveClip.cpp:921
sampleCount GetSequenceSamplesCount() const
Returns the total number of samples in underlying sequence (not counting the cutlines)
Definition: WaveClip.cpp:873
int GetRate() const
Definition: WaveClip.h:140
A Track that contains audio waveform data.
Definition: WaveTrack.h:51
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
long long as_long_long() const
Definition: SampleCount.h:48
double as_double() const
Definition: SampleCount.h:46
void ComputeSpectrogramGainFactors(size_t fftLen, double rate, int frequencyGain, std::vector< float > &gainFactors)
static void ComputeSpectrumUsingRealFFTf(float *__restrict buffer, const FFTParam *hFFT, const float *__restrict window, size_t len, float *__restrict out)
constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept
Definition: fast_float.h:1454
STL namespace.
size_t Points
Definition: RealFFTf.h:10
ArrayOf< int > BitReversed
Definition: RealFFTf.h:8
static WaveClipSpectrumCache & Get(const WaveClip &clip)
std::unique_ptr< SpecCache > mSpecCache
~WaveClipSpectrumCache() override
bool GetSpectrogram(const WaveClip &clip, SampleTrackCache &cache, const float *&spectrogram, const sampleCount *&where, size_t numPixels, double t0, double pixelsPerSecond)
void Invalidate() override
void MarkChanged() override