348{
349 auto &dc = context.
dc;
351 bool onBrushTool = artist->onBrushTool;
352 const auto &selectedRegion = *artist->pSelectedRegion;
353 const auto &zoomInfo = *artist->pZoomInfo;
354
355#ifdef PROFILE_WAVEFORM
357#endif
358
359
360
362 {
365 return;
366 }
367
370
371 enum { DASH_LENGTH = 10 };
372
374 const wxRect &hiddenMid =
params.hiddenMid;
375
376
377 if (hiddenMid.width <= 0) {
378 return;
379 }
380
381 const double &t0 =
params.t0;
383
386 const double &averagePixelsPerSecond =
params.averagePixelsPerSecond;
389 const double &hiddenLeftOffset =
params.hiddenLeftOffset;
390 const double &leftOffset =
params.leftOffset;
391 const wxRect &mid =
params.mid;
392
395 freqLo = selectedRegion.f0();
396 freqHi = selectedRegion.f1();
397
398 const int &colorScheme =
settings.colorScheme;
401
402#ifdef EXPERIMENTAL_FIND_NOTES
403 const bool &fftFindNotes =
settings.fftFindNotes;
404 const double &findNotesMinA =
settings.findNotesMinA;
405 const int &numberOfMaxima =
settings.numberOfMaxima;
406 const bool &findNotesQuantize =
settings.findNotesQuantize;
407#endif
408#ifdef EXPERIMENTAL_FFT_Y_GRID
409 const bool &fftYGrid =
settings.fftYGrid;
410#endif
411
412 dc.SetPen(*wxTRANSPARENT_PEN);
413
414
415
416
417
418 wxImage image((
int)mid.width, (
int)mid.height);
419 if (!image.IsOk())
420 return;
421#ifdef EXPERIMENTAL_SPECTROGRAM_OVERLAY
422 image.SetAlpha();
423 unsigned char *alpha = image.GetAlpha();
424#endif
425 unsigned char *data = image.GetData();
426
427 const auto half =
settings.GetFFTLength() / 2;
428 const double binUnit =
sampleRate / (2 * half);
429 const float *freq = 0;
432 clip, freq,
settings, where, (
size_t)hiddenMid.width, t0,
433 averagePixelsPerSecond);
435
436 float minFreq, maxFreq;
438
440
441
442
443 float *bins = (float*)alloca(sizeof(*bins)*(hiddenMid.height + 1));
444 {
446
448 float nextBin = std::max( 0.0f,
std::min(
float(nBins - 1),
449 settings.findBin( *it, binUnit ) ) );
450
451 int yy;
452 for (yy = 0; yy < hiddenMid.height; ++yy) {
453 bins[yy] = nextBin;
454 nextBin = std::max( 0.0f,
std::min(
float(nBins - 1),
455 settings.findBin( *++it, binUnit ) ) );
456 }
457 bins[yy] = nextBin;
458 }
459
460#ifdef EXPERIMENTAL_FFT_Y_GRID
461 const float
462 log2 = logf(2.0f),
463 scale2 = (lmax - lmin) / log2,
464 lmin2 = lmin / log2;
465
467 for (int yy = 0; yy < mid.height; ++yy) {
468 float n = (float(yy) / mid.height*scale2 - lmin2) * 12;
469 float n2 = (float(yy + 1) / mid.height*scale2 - lmin2) * 12;
470 float f = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n / 12.0f + lmin2);
471 float f2 = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n2 / 12.0f + lmin2);
472 n = logf(f / 440) / log2 * 12;
473 n2 = logf(f2 / 440) / log2 * 12;
474 if (floor(n) < floor(n2))
475 yGrid[yy] = true;
476 else
477 yGrid[yy] = false;
478 }
479#endif
480
483 if (!updated && specPxCache &&
484 ((int)specPxCache->len == hiddenMid.height * hiddenMid.width)
485 && scaleType == specPxCache->scaleType
486 && gain == specPxCache->gain
487 && range == specPxCache->range
488 && minFreq == specPxCache->minFreq
489 && maxFreq == specPxCache->maxFreq
490#ifdef EXPERIMENTAL_FFT_Y_GRID
491 && fftYGrid==fftYGridOld
492#endif
493#ifdef EXPERIMENTAL_FIND_NOTES
494 && fftFindNotes == artist->fftFindNotesOld
495 && findNotesMinA == artist->findNotesMinAOld
496 && numberOfMaxima == artist->findNotesNOld
497 && findNotesQuantize == artist->findNotesQuantizeOld
498#endif
499 ) {
500
501
502 }
503 else {
504
505 specPxCache = std::make_unique<SpecPxCache>(hiddenMid.width * hiddenMid.height);
506 specPxCache->scaleType = scaleType;
507 specPxCache->gain = gain;
508 specPxCache->range = range;
509 specPxCache->minFreq = minFreq;
510 specPxCache->maxFreq = maxFreq;
511#ifdef EXPERIMENTAL_FIND_NOTES
512 artist->fftFindNotesOld = fftFindNotes;
513 artist->findNotesMinAOld = findNotesMinA;
514 artist->findNotesNOld = numberOfMaxima;
515 artist->findNotesQuantizeOld = findNotesQuantize;
516#endif
517
518#ifdef EXPERIMENTAL_FIND_NOTES
519 float log2 = logf( 2.0f ),
520 lmin = logf( minFreq ), lmax = logf( maxFreq ), scale = lmax - lmin,
521 lmins = lmin,
522 lmaxs = lmax
523 ;
524#endif
525
526#ifdef EXPERIMENTAL_FIND_NOTES
527 int maxima[128];
528 float maxima0[128], maxima1[128];
529 const float
531 bin2f = 1.0f / f2bin,
532 minDistance = powf(2.0f, 2.0f / 12.0f),
533 i0 = expf(lmin) / binUnit,
534 i1 = expf(scale + lmin) / binUnit,
535 minColor = 0.0f;
536 const size_t maxTableSize = 1024;
538#endif
539
540#ifdef _OPENMP
541#pragma omp parallel for
542#endif
543 for (int xx = 0; xx < hiddenMid.width; ++xx) {
544#ifdef EXPERIMENTAL_FIND_NOTES
545 int maximas = 0;
546 const int x0 = nBins * xx;
547 if (fftFindNotes) {
548 for (int i = maxTableSize - 1; i >= 0; i--)
549 indexes[i] = -1;
550
551
552 for (int i = (int)(i0); i < (int)(i1); i++) {
553 float freqi = freq[x0 + (int)(i)];
554 int value = (int)((freqi + gain + range) / range*(maxTableSize - 1));
555 if (value < 0)
556 value = 0;
557 if (value >= maxTableSize)
558 value = maxTableSize - 1;
559 indexes[value] = i;
560 }
561
562 for (int i = maxTableSize - 1; i >= 0; i--) {
563 int index = indexes[i];
564 if (index >= 0) {
565 float freqi = freq[x0 + index];
566 if (freqi < findNotesMinA)
567 break;
568
569 bool ok = true;
570 for (int m = 0; m < maximas; m++) {
571
572 float maxm = maxima[m];
573 if (maxm / index < minDistance && index / maxm < minDistance) {
574 ok = false;
575 break;
576 }
577 }
578 if (ok) {
579 maxima[maximas++] = index;
580 if (maximas >= numberOfMaxima)
581 break;
582 }
583 }
584 }
585
586
587#define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*hiddenMid.height
588
589
590 for (int i = 0; i < maximas; i++) {
591 int index = maxima[i];
592 float f = float(index)*bin2f;
593 if (findNotesQuantize)
594 {
595 f = expf((int)(log(f / 440) / log2 * 12 - 0.5) / 12.0f*log2) * 440;
596 maxima[i] = f*f2bin;
597 }
598 float f0 = expf((log(f / 440) / log2 * 24 - 1) / 24.0f*log2) * 440;
599 maxima0[i] = f2pix(f0);
600 float f1 = expf((log(f / 440) / log2 * 24 + 1) / 24.0f*log2) * 440;
601 maxima1[i] = f2pix(f1);
602 }
603 }
604
605 int it = 0;
606 bool inMaximum = false;
607#endif
608
609 for (int yy = 0; yy < hiddenMid.height; ++yy) {
610 const float bin = bins[yy];
611 const float nextBin = bins[yy+1];
612
615 (freq + nBins * xx, bin, nextBin, nBins, autocorrelation, gain, range);
616 specPxCache->values[xx * hiddenMid.height + yy] = value;
617 }
618 else {
619 float value;
620
621#ifdef EXPERIMENTAL_FIND_NOTES
622 if (fftFindNotes) {
623 if (it < maximas) {
624 float i0 = maxima0[it];
625 if (yy >= i0)
626 inMaximum = true;
627
628 if (inMaximum) {
629 float i1 = maxima1[it];
630 if (yy + 1 <= i1) {
631 value =
findValue(freq + x0, bin, nextBin, nBins, autocorrelation, gain, range);
632 if (value < findNotesMinA)
633 value = minColor;
634 }
635 else {
636 it++;
637 inMaximum = false;
638 value = minColor;
639 }
640 }
641 else {
642 value = minColor;
643 }
644 }
645 else
646 value = minColor;
647 }
648 else
649#endif
650 {
652 (freq + nBins * xx, bin, nextBin, nBins, autocorrelation, gain, range);
653 }
654 specPxCache->values[xx * hiddenMid.height + yy] = value;
655 }
656 }
657 }
658 }
659
660 float selBinLo =
settings.findBin( freqLo, binUnit);
661 float selBinHi =
settings.findBin( freqHi, binUnit);
662 float selBinCenter = (freqLo < 0 || freqHi < 0)
663 ? -1
665
666 const bool isSpectral =
settings.SpectralSelectionEnabled();
668 const int begin = hidden
669 ? 0
670 : std::max(0, (int)(zoomInfo.GetFisheyeLeftBoundary(-leftOffset)));
671 const int end = hidden
672 ? 0
673 :
std::min(mid.width, (
int)(zoomInfo.GetFisheyeRightBoundary(-leftOffset)));
674 const size_t numPixels = std::max(0,
end -
begin);
675
677
678
680
681 if (numPixels > 0) {
682 for (
int ii =
begin; ii <
end; ++ii) {
683 const double time = zoomInfo.PositionToTime(ii, -leftOffset) - playStartTime;
686 }
689 0
690 );
691 }
692
693
696
697
698 int fisheyeLeft = zoomInfo.GetFisheyeLeftBoundary(-leftOffset);
699
700
701 int selectedX = zoomInfo.TimeToPosition(selectedRegion.t0(), -leftOffset);
702
703#ifdef _OPENMP
704#pragma omp parallel for
705#endif
706
708 int windowSize = mpSpectralData->GetWindowSize();
709 int hopSize = mpSpectralData->GetHopSize();
710 double sr = mpSpectralData->GetSR();
711 auto &dataHistory = mpSpectralData->dataHistory;
712
713
714 dataHistory.push_back(mpSpectralData->dataBuffer);
715
716
717 std::map<long long, std::set<int>> hopBinMap;
718 for(auto vecIter = dataHistory.begin(); vecIter != dataHistory.end(); ++vecIter){
719 for(const auto &hopMap: *vecIter){
720 for(const auto &binNum: hopMap.second)
721 hopBinMap[hopMap.first].insert(binNum);
722 }
723 }
724
725
726 auto yyToFreqBin = [&](int yy){
727 const double p = double(yy) / hiddenMid.height;
728 float convertedFreq = numberScale.PositionToValue(p);
729 float convertedFreqBinNum = convertedFreq / (sr / windowSize);
730
731
732
733
734
735 return static_cast<int>(
lrintf(convertedFreqBinNum));
736 };
737
738 for (int xx = 0; xx < mid.width; ++xx) {
739 int correctedX = xx + leftOffset - hiddenLeftOffset;
740
741
742
743 float* uncached;
744 if (!zoomInfo.InFisheye(xx, -leftOffset)) {
745 uncached = 0;
746 }
747 else {
748 int specIndex = (xx - fisheyeLeft) * nBins;
749 wxASSERT(specIndex >= 0 && specIndex < (
int)specCache.
freq.size());
750 uncached = &specCache.
freq[specIndex];
751 }
752
753
754
757 (zoomInfo.PositionToTime(xx, -leftOffset) - playStartTime));
758
761 (zoomInfo.PositionToTime(xx + 1, -leftOffset) - playStartTime));
762
763 bool maybeSelected = ssel0 <= w0 && w1 < ssel1;
764 maybeSelected = maybeSelected || (xx == selectedX);
765
766
767 std::set<int> *pSelectedBins = nullptr;
768 std::set<int>::iterator freqBinIter;
769 auto advanceFreqBinIter = [&](int nextBinRounded){
770 while (freqBinIter != pSelectedBins->end() &&
771 *freqBinIter < nextBinRounded)
772 ++freqBinIter;
773 };
774
775 bool hitHopNum = false;
776 if (onBrushTool) {
777 int convertedHopNum = (w0.as_long_long() + hopSize / 2) / hopSize;
778 hitHopNum = (hopBinMap.find(convertedHopNum) != hopBinMap.end());
779 if(hitHopNum) {
780 pSelectedBins = &hopBinMap[convertedHopNum];
781 freqBinIter = pSelectedBins->begin();
782 advanceFreqBinIter(yyToFreqBin(0));
783 }
784 }
785
786 for (int yy = 0; yy < hiddenMid.height; ++yy) {
787 if(onBrushTool)
788 maybeSelected = false;
789 const float bin = bins[yy];
790 const float nextBin = bins[yy+1];
791 auto binRounded = yyToFreqBin(yy);
792 auto nextBinRounded = yyToFreqBin(yy + 1);
793
794 if(hitHopNum
795 && freqBinIter != pSelectedBins->end()
796 && binRounded == *freqBinIter)
797 maybeSelected = true;
798
799 if (hitHopNum)
800 advanceFreqBinIter(nextBinRounded);
801
802
803
804
805
807
808
809 if (maybeSelected) {
810 selected =
812 (xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral);
814
816 }
817
818 const float value = uncached
819 ?
findValue(uncached, bin, nextBin, nBins, autocorrelation, gain, range)
820 : specPxCache->
values[correctedX * hiddenMid.height + yy];
821
822 unsigned char rv, gv, bv;
824
825#ifdef EXPERIMENTAL_FFT_Y_GRID
826 if (fftYGrid && yGrid[yy]) {
827 rv /= 1.1f;
828 gv /= 1.1f;
829 bv /= 1.1f;
830 }
831#endif
832 int px = ((mid.height - 1 - yy) * mid.width + xx);
833#ifdef EXPERIMENTAL_SPECTROGRAM_OVERLAY
834
835 alpha[px]= wxMin( 200, (value+0.3) * 500) ;
836#endif
837 px *=3;
838 data[px++] = rv;
839 data[px++] = gv;
840 data[px] = bv;
841 }
842 }
843
844 dataHistory.pop_back();
845 wxBitmap converted = wxBitmap(image);
846
847 wxMemoryDC memDC;
848
849 memDC.SelectObject(converted);
850
851 dc.Blit(mid.x, mid.y, mid.width, mid.height, &memDC, 0, 0, wxCOPY, FALSE);
852
853
854
855 {
858 }
859}
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....
static const int UndefinedFrequency
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)
std::vector< float > freq
std::vector< sampleCount > where
void GetBounds(const WaveChannel &wc, 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)
static TrackArtist * Get(TrackPanelDrawingContext &)
bool GetSelected() const
Selectedness is always the same for all channels of a group.
static bool ClipDetailsVisible(const ClipTimes &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
size_t GetChannelIndex() const
int GetRate() const override
double GetPlayStartTime() const override
double GetStretchRatio() const override
Positions or offsets within audio files need a wide type.
AUDACITY_DLL_API void DrawClipFolded(wxDC &dc, const wxRect &rect)
AUDACITY_DLL_API void DrawClipEdges(wxDC &dc, const wxRect &clipRect, bool selected=false)
constexpr auto sampleRate
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)
std::pair< sampleCount, sampleCount > GetSelectedSampleIndices(const SelectedRegion &selectedRegion, const WaveChannelInterval &clip, bool trackIsSelected)
__finl float_x4 __vecc sqrt(const float_x4 &a)
static wxRect GetClipRect(const ClipTimes &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect, bool *outShowSamples=nullptr)
bool GetSpectrogram(const WaveChannelInterval &clip, const float *&spectrogram, SpectrogramSettings &spectrogramSettings, const sampleCount *&where, size_t numPixels, double t0, double pixelsPerSecond)
static WaveClipSpectrumCache & Get(const WaveChannelInterval &clip)