324{
325 auto &dc = context.
dc;
327 bool onBrushTool = artist->onBrushTool;
328 const auto &selectedRegion = *artist->pSelectedRegion;
329 const auto &zoomInfo = *artist->pZoomInfo;
330
331#ifdef PROFILE_WAVEFORM
333#endif
334
335
336
338 {
341 return;
342 }
343
344 const auto track =
346 if (!track)
347
348
349
350
351 return;
352
355
356 enum { DASH_LENGTH = 10 };
357
359 true, track, clip, rect, selectedRegion, zoomInfo };
360 const wxRect &hiddenMid =
params.hiddenMid;
361
362
363 if (hiddenMid.width <= 0) {
364 return;
365 }
366
367 const double &t0 =
params.t0;
368 const double &tOffset =
params.tOffset;
369 const auto &ssel0 =
params.ssel0;
370 const auto &ssel1 =
params.ssel1;
371 const double &averagePixelsPerSample =
params.averagePixelsPerSample;
372 const double &rate =
params.rate;
373 const double &hiddenLeftOffset =
params.hiddenLeftOffset;
374 const double &leftOffset =
params.leftOffset;
375 const wxRect &mid =
params.mid;
376
379#ifdef EXPERIMENTAL_SPECTRAL_EDITING
380 freqLo = selectedRegion.f0();
381 freqHi = selectedRegion.f1();
382#endif
383
384 const int &colorScheme =
settings.colorScheme;
387
388#ifdef EXPERIMENTAL_FIND_NOTES
389 const bool &fftFindNotes =
settings.fftFindNotes;
390 const double &findNotesMinA =
settings.findNotesMinA;
391 const int &numberOfMaxima =
settings.numberOfMaxima;
392 const bool &findNotesQuantize =
settings.findNotesQuantize;
393#endif
394#ifdef EXPERIMENTAL_FFT_Y_GRID
395 const bool &fftYGrid =
settings.fftYGrid;
396#endif
397
398 dc.SetPen(*wxTRANSPARENT_PEN);
399
400
401
402
403
404 wxImage image((
int)mid.width, (
int)mid.height);
405 if (!image.IsOk())
406 return;
407#ifdef EXPERIMENTAL_SPECTROGRAM_OVERLAY
408 image.SetAlpha();
409 unsigned char *alpha = image.GetAlpha();
410#endif
411 unsigned char *data = image.GetData();
412
413 const auto half =
settings.GetFFTLength() / 2;
414 const double binUnit = rate / (2 * half);
415 const float *freq = 0;
417 bool updated;
418 {
419 const double pps = averagePixelsPerSample * rate;
421 waveTrackCache, freq, where,
422 (size_t)hiddenMid.width,
423 t0, pps);
424 }
426
427 float minFreq, maxFreq;
430
432
433
434
435 float *bins = (float*)alloca(sizeof(*bins)*(hiddenMid.height + 1));
436 {
438
440 float nextBin = std::max( 0.0f,
std::min(
float(nBins - 1),
441 settings.findBin( *it, binUnit ) ) );
442
443 int yy;
444 for (yy = 0; yy < hiddenMid.height; ++yy) {
445 bins[yy] = nextBin;
446 nextBin = std::max( 0.0f,
std::min(
float(nBins - 1),
447 settings.findBin( *++it, binUnit ) ) );
448 }
449 bins[yy] = nextBin;
450 }
451
452#ifdef EXPERIMENTAL_FFT_Y_GRID
453 const float
454 log2 = logf(2.0f),
455 scale2 = (lmax - lmin) / log2,
456 lmin2 = lmin / log2;
457
459 for (int yy = 0; yy < mid.height; ++yy) {
460 float n = (float(yy) / mid.height*scale2 - lmin2) * 12;
461 float n2 = (float(yy + 1) / mid.height*scale2 - lmin2) * 12;
462 float f = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n / 12.0f + lmin2);
463 float f2 = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n2 / 12.0f + lmin2);
464 n = logf(f / 440) / log2 * 12;
465 n2 = logf(f2 / 440) / log2 * 12;
466 if (floor(n) < floor(n2))
467 yGrid[yy] = true;
468 else
469 yGrid[yy] = false;
470 }
471#endif
472
474 if (!updated && clipCache.mSpecPxCache->valid &&
475 ((int)clipCache.mSpecPxCache->len == hiddenMid.height * hiddenMid.width)
476 && scaleType == clipCache.mSpecPxCache->scaleType
477 && gain == clipCache.mSpecPxCache->gain
478 && range == clipCache.mSpecPxCache->range
479 && minFreq == clipCache.mSpecPxCache->minFreq
480 && maxFreq == clipCache.mSpecPxCache->maxFreq
481#ifdef EXPERIMENTAL_FFT_Y_GRID
482 && fftYGrid==fftYGridOld
483#endif
484#ifdef EXPERIMENTAL_FIND_NOTES
485 && fftFindNotes == artist->fftFindNotesOld
486 && findNotesMinA == artist->findNotesMinAOld
487 && numberOfMaxima == artist->findNotesNOld
488 && findNotesQuantize == artist->findNotesQuantizeOld
489#endif
490 ) {
491
492
493 }
494 else {
495
496 clipCache.mSpecPxCache = std::make_unique<SpecPxCache>(hiddenMid.width * hiddenMid.height);
497 clipCache.mSpecPxCache->valid = true;
498 clipCache.mSpecPxCache->scaleType = scaleType;
499 clipCache.mSpecPxCache->gain = gain;
500 clipCache.mSpecPxCache->range = range;
501 clipCache.mSpecPxCache->minFreq = minFreq;
502 clipCache.mSpecPxCache->maxFreq = maxFreq;
503#ifdef EXPERIMENTAL_FIND_NOTES
504 artist->fftFindNotesOld = fftFindNotes;
505 artist->findNotesMinAOld = findNotesMinA;
506 artist->findNotesNOld = numberOfMaxima;
507 artist->findNotesQuantizeOld = findNotesQuantize;
508#endif
509
510#ifdef EXPERIMENTAL_FIND_NOTES
511 float log2 = logf( 2.0f ),
512 lmin = logf( minFreq ), lmax = logf( maxFreq ), scale = lmax - lmin,
513 lmins = lmin,
514 lmaxs = lmax
515 ;
516#endif
517
518#ifdef EXPERIMENTAL_FIND_NOTES
519 int maxima[128];
520 float maxima0[128], maxima1[128];
521 const float
522 f2bin = half / (rate / 2.0f),
523 bin2f = 1.0f / f2bin,
524 minDistance = powf(2.0f, 2.0f / 12.0f),
525 i0 = expf(lmin) / binUnit,
526 i1 = expf(scale + lmin) / binUnit,
527 minColor = 0.0f;
528 const size_t maxTableSize = 1024;
530#endif
531
532#ifdef _OPENMP
533#pragma omp parallel for
534#endif
535 for (int xx = 0; xx < hiddenMid.width; ++xx) {
536#ifdef EXPERIMENTAL_FIND_NOTES
537 int maximas = 0;
538 const int x0 = nBins * xx;
539 if (fftFindNotes) {
540 for (int i = maxTableSize - 1; i >= 0; i--)
541 indexes[i] = -1;
542
543
544 for (int i = (int)(i0); i < (int)(i1); i++) {
545 float freqi = freq[x0 + (int)(i)];
546 int value = (int)((freqi + gain + range) / range*(maxTableSize - 1));
547 if (value < 0)
548 value = 0;
549 if (value >= maxTableSize)
550 value = maxTableSize - 1;
551 indexes[value] = i;
552 }
553
554 for (int i = maxTableSize - 1; i >= 0; i--) {
555 int index = indexes[i];
556 if (index >= 0) {
557 float freqi = freq[x0 + index];
558 if (freqi < findNotesMinA)
559 break;
560
561 bool ok = true;
562 for (int m = 0; m < maximas; m++) {
563
564 float maxm = maxima[m];
565 if (maxm / index < minDistance && index / maxm < minDistance) {
566 ok = false;
567 break;
568 }
569 }
570 if (ok) {
571 maxima[maximas++] = index;
572 if (maximas >= numberOfMaxima)
573 break;
574 }
575 }
576 }
577
578
579#define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*hiddenMid.height
580
581
582 for (int i = 0; i < maximas; i++) {
583 int index = maxima[i];
584 float f = float(index)*bin2f;
585 if (findNotesQuantize)
586 {
587 f = expf((int)(log(f / 440) / log2 * 12 - 0.5) / 12.0f*log2) * 440;
588 maxima[i] = f*f2bin;
589 }
590 float f0 = expf((log(f / 440) / log2 * 24 - 1) / 24.0f*log2) * 440;
591 maxima0[i] = f2pix(f0);
592 float f1 = expf((log(f / 440) / log2 * 24 + 1) / 24.0f*log2) * 440;
593 maxima1[i] = f2pix(f1);
594 }
595 }
596
597 int it = 0;
598 bool inMaximum = false;
599#endif
600
601 for (int yy = 0; yy < hiddenMid.height; ++yy) {
602 const float bin = bins[yy];
603 const float nextBin = bins[yy+1];
604
607 (freq + nBins * xx, bin, nextBin, nBins, autocorrelation, gain, range);
608 clipCache.mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
609 }
610 else {
611 float value;
612
613#ifdef EXPERIMENTAL_FIND_NOTES
614 if (fftFindNotes) {
615 if (it < maximas) {
616 float i0 = maxima0[it];
617 if (yy >= i0)
618 inMaximum = true;
619
620 if (inMaximum) {
621 float i1 = maxima1[it];
622 if (yy + 1 <= i1) {
623 value =
findValue(freq + x0, bin, nextBin, nBins, autocorrelation, gain, range);
624 if (value < findNotesMinA)
625 value = minColor;
626 }
627 else {
628 it++;
629 inMaximum = false;
630 value = minColor;
631 }
632 }
633 else {
634 value = minColor;
635 }
636 }
637 else
638 value = minColor;
639 }
640 else
641#endif
642 {
644 (freq + nBins * xx, bin, nextBin, nBins, autocorrelation, gain, range);
645 }
646 clipCache.mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
647 }
648 }
649 }
650 }
651
652 float selBinLo =
settings.findBin( freqLo, binUnit);
653 float selBinHi =
settings.findBin( freqHi, binUnit);
654 float selBinCenter = (freqLo < 0 || freqHi < 0)
655 ? -1
656 :
settings.findBin( sqrt(freqLo * freqHi), binUnit );
657
658 const bool isSpectral =
settings.SpectralSelectionEnabled();
660 const int begin = hidden
661 ? 0
662 : std::max(0, (int)(zoomInfo.GetFisheyeLeftBoundary(-leftOffset)));
663 const int end = hidden
664 ? 0
665 :
std::min(mid.width, (
int)(zoomInfo.GetFisheyeRightBoundary(-leftOffset)));
666 const size_t numPixels = std::max(0,
end -
begin);
667
669
670
672
673 if (numPixels > 0) {
674 for (
int ii =
begin; ii <
end; ++ii) {
675 const double time = zoomInfo.PositionToTime(ii, -leftOffset) - tOffset;
677 }
680 0, 0, numPixels,
682 tOffset, rate,
683 0
684 );
685 }
686
687
690
691
692 int fisheyeLeft = zoomInfo.GetFisheyeLeftBoundary(-leftOffset);
693
694
695 int selectedX = zoomInfo.TimeToPosition(selectedRegion.t0(), -leftOffset);
696
697#ifdef _OPENMP
698#pragma omp parallel for
699#endif
700
702 int windowSize = mpSpectralData->GetWindowSize();
703 int hopSize = mpSpectralData->GetHopSize();
704 double sr = mpSpectralData->GetSR();
705 auto &dataHistory = mpSpectralData->dataHistory;
706
707
708 dataHistory.push_back(mpSpectralData->dataBuffer);
709
710
711 std::map<long long, std::set<int>> hopBinMap;
712 for(auto vecIter = dataHistory.begin(); vecIter != dataHistory.end(); ++vecIter){
713 for(const auto &hopMap: *vecIter){
714 for(const auto &binNum: hopMap.second)
715 hopBinMap[hopMap.first].insert(binNum);
716 }
717 }
718
719
720 auto yyToFreqBin = [&](int yy){
721 const double p = double(yy) / hiddenMid.height;
722 float convertedFreq = numberScale.PositionToValue(p);
723 float convertedFreqBinNum = convertedFreq / (sr / windowSize);
724
725
726
727
728
729 return static_cast<int>(
lrintf(convertedFreqBinNum));
730 };
731
732 for (int xx = 0; xx < mid.width; ++xx) {
733 int correctedX = xx + leftOffset - hiddenLeftOffset;
734
735
736
737 float* uncached;
738 if (!zoomInfo.InFisheye(xx, -leftOffset)) {
739 uncached = 0;
740 }
741 else {
742 int specIndex = (xx - fisheyeLeft) * nBins;
743 wxASSERT(specIndex >= 0 && specIndex < (
int)specCache.
freq.size());
744 uncached = &specCache.
freq[specIndex];
745 }
746
747
748
750 (zoomInfo.PositionToTime(xx, -leftOffset) - tOffset));
751
753 (zoomInfo.PositionToTime(xx+1, -leftOffset) - tOffset));
754
755 bool maybeSelected = ssel0 <= w0 && w1 < ssel1;
756 maybeSelected = maybeSelected || (xx == selectedX);
757
758
759 std::set<int> *pSelectedBins = nullptr;
760 std::set<int>::iterator freqBinIter;
761 auto advanceFreqBinIter = [&](int nextBinRounded){
762 while (freqBinIter != pSelectedBins->end() &&
763 *freqBinIter < nextBinRounded)
764 ++freqBinIter;
765 };
766
767 bool hitHopNum = false;
768 if (onBrushTool) {
769 int convertedHopNum = (w0.as_long_long() + hopSize / 2) / hopSize;
770 hitHopNum = (hopBinMap.find(convertedHopNum) != hopBinMap.end());
771 if(hitHopNum) {
772 pSelectedBins = &hopBinMap[convertedHopNum];
773 freqBinIter = pSelectedBins->begin();
774 advanceFreqBinIter(yyToFreqBin(0));
775 }
776 }
777
778 for (int yy = 0; yy < hiddenMid.height; ++yy) {
779 if(onBrushTool)
780 maybeSelected = false;
781 const float bin = bins[yy];
782 const float nextBin = bins[yy+1];
783 auto binRounded = yyToFreqBin(yy);
784 auto nextBinRounded = yyToFreqBin(yy + 1);
785
786 if(hitHopNum
787 && freqBinIter != pSelectedBins->end()
788 && binRounded == *freqBinIter)
789 maybeSelected = true;
790
791 if (hitHopNum)
792 advanceFreqBinIter(nextBinRounded);
793
794
795
796
797
799
800
801 if (maybeSelected) {
802 selected =
804 (xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral);
806
808 }
809
810 const float value = uncached
811 ?
findValue(uncached, bin, nextBin, nBins, autocorrelation, gain, range)
812 : clipCache.mSpecPxCache->
values[correctedX * hiddenMid.height + yy];
813
814 unsigned char rv, gv, bv;
816
817#ifdef EXPERIMENTAL_FFT_Y_GRID
818 if (fftYGrid && yGrid[yy]) {
819 rv /= 1.1f;
820 gv /= 1.1f;
821 bv /= 1.1f;
822 }
823#endif
824 int px = ((mid.height - 1 - yy) * mid.width + xx);
825#ifdef EXPERIMENTAL_SPECTROGRAM_OVERLAY
826
827 alpha[px]= wxMin( 200, (value+0.3) * 500) ;
828#endif
829 px *=3;
830 data[px++] = rv;
831 data[px++] = gv;
832 data[px] = bv;
833 }
834 }
835
836 dataHistory.pop_back();
837 wxBitmap converted = wxBitmap(image);
838
839 wxMemoryDC memDC;
840
841 memDC.SelectObject(converted);
842
843 dc.Blit(mid.x, mid.y, mid.width, mid.height, &memDC, 0, 0, wxCOPY, FALSE);
844
845
846
847 {
850 }
851}
void GetColorGradient(float value, AColor::ColorGradientChoice selected, int colorScheme, unsigned char *__restrict red, unsigned char *__restrict green, unsigned char *__restrict blue)
EffectDistortionSettings params
static Settings & settings()
@ ColorGradientUnselected
static void PreComputeGradient()
static bool gradient_inited
A simple profiler to measure the average time lengths that a particular task/function takes....
const std::shared_ptr< const SampleTrack > & GetTrack() const
static const int UndefinedFrequency
void Populate(const SpectrogramSettings &settings, SampleTrackCache &waveTrackCache, int copyBegin, int copyEnd, size_t numPixels, sampleCount numSamples, double offset, double rate, double pixelsPerSecond)
void Grow(size_t len_, SpectrogramSettings &settings, double pixelsPerSecond, double start_)
std::vector< float > freq
std::vector< sampleCount > where
void GetBounds(const WaveTrack &wt, float &min, float &max) const
static SpectrogramBounds & Get(WaveTrack &track)
Get either the global default settings, or the track's own if previously created.
static SpectrogramSettings & Get(const WaveTrack &track)
Mutative access to attachment even if the track argument is const.
static TrackArtist * Get(TrackPanelDrawingContext &)
sampleCount GetPlaySamplesCount() const
A Track that contains audio waveform data.
static bool ClipDetailsVisible(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
Positions or offsets within audio files need a wide type.
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
AUDACITY_DLL_API void DrawClipFolded(wxDC &dc, const wxRect &rect)
AUDACITY_DLL_API void DrawClipEdges(wxDC &dc, const wxRect &clipRect, bool selected=false)
AColor::ColorGradientChoice ChooseColorSet(float bin0, float bin1, float selBinLo, float selBinCenter, float selBinHi, int dashCount, bool isSpectral)
static float findValue(const float *spectrum, float bin0, float bin1, unsigned nBins, bool autocorrelation, int gain, int range)
static wxRect GetClipRect(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect, bool *outShowSamples=nullptr)
static WaveClipSpectrumCache & Get(const WaveClip &clip)
bool GetSpectrogram(const WaveClip &clip, SampleTrackCache &cache, const float *&spectrogram, const sampleCount *&where, size_t numPixels, double t0, double pixelsPerSecond)