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