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