Audacity 3.2.0
SpectrumView.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5SpectrumView.cpp
6
7Paul Licameli split from WaveTrackView.cpp
8
9**********************************************************************/
10
11
12#include "SpectrumView.h"
13
14#include "SpectralDataManager.h" // Cycle :-(
15#include "SpectrumCache.h"
16
17#include "Sequence.h"
18#include "Spectrum.h"
19
21#include "WaveTrackView.h"
23
24#include "../../../ui/BrushHandle.h"
25
26#include "AColor.h"
27#include "Prefs.h"
28#include "NumberScale.h"
29#include "../../../../TrackArt.h"
30#include "../../../../TrackArtist.h"
31#include "../../../../TrackPanelDrawingContext.h"
32#include "ViewInfo.h"
33#include "../../../../WaveClip.h"
34#include "../../../../WaveTrack.h"
35#include "../../../../prefs/SpectrogramSettings.h"
36#include "../../../../ProjectSettings.h"
37#include "SampleTrackCache.h"
38
39#include <wx/dcmemory.h>
40#include <wx/graphics.h>
41
42#include "float_cast.h"
43
44class BrushHandle;
45class SpectralData;
46
49 { wxT("Spectrogram"), XXO("&Spectrogram") }
50};
51
53
55 auto wt = static_cast<WaveTrack*>( FindTrack().get() );
56 mpSpectralData = std::make_shared<SpectralData>(wt->GetRate());
57 mOnBrushTool = false;
58}
59
61
63{
64 return true;
65}
66
68public:
70 : mView{ view }
71 {}
72
73 void Init( AudacityProject &project, bool clearAll ) override
74 {
75 mpProject = &project;
76 ForAll( project, [this, clearAll](SpectrumView &view){
77 auto pOldData = view.mpSpectralData;
78 if (clearAll) {
79 auto &pNewData = view.mpBackupSpectralData =
80 std::make_shared<SpectralData>(pOldData->GetSR());
81 pNewData->CopyFrom(*pOldData);
82 pOldData->clearAllData();
83 }
84 else {
85 // Back up one view only
86 if (&mView == &view) {
87 auto &pNewData = view.mpBackupSpectralData =
88 std::make_shared<SpectralData>(pOldData->GetSR());
89 pNewData->CopyFrom( *pOldData );
90 }
91 else
92 view.mpBackupSpectralData = {};
93 }
94 });
95 }
96
98 {
99 if (mpProject)
100 ForAll( *mpProject, [this](SpectrumView &view){
101 if (mCommitted) {
102 // Discard all backups
103 view.mpBackupSpectralData = {};
104 }
105 else {
106 // Restore all
107 if (auto &pBackupData = view.mpBackupSpectralData) {
108 view.mpSpectralData->CopyFrom(*pBackupData);
109 pBackupData.reset();
110 }
111 }
112 });
113 }
114
115private:
118};
119
120// This always hits, but details of the hit vary with mouse position and
121// key state.
123 std::weak_ptr<BrushHandle> &holder,
124 const TrackPanelMouseState &st, const AudacityProject *pProject,
125 const std::shared_ptr<SpectrumView> &pTrackView,
126 const std::shared_ptr<SpectralData> &mpData)
127{
128 const auto &viewInfo = ViewInfo::Get( *pProject );
129 auto &projectSettings = ProjectSettings::Get( *pProject );
130 auto result = std::make_shared<BrushHandle>(
131 std::make_shared<SpectrumView::SpectralDataSaver>(*pTrackView),
132 pTrackView, TrackList::Get( *pProject ),
133 st, viewInfo, mpData, projectSettings);
134
135 result = AssignUIHandlePtr(holder, result);
136
137 //Make sure we are within the selected track
138 // Adjusting the selection edges can be turned off in
139 // the preferences...
140 auto pTrack = pTrackView->FindTrack();
141 if (!pTrack->GetSelected() || !viewInfo.bAdjustSelectionEdges)
142 {
143 return result;
144 }
145
146 return result;
147}
148
150 std::function<void(SpectrumView &view)> fn )
151{
152 if (!fn)
153 return;
154 for ( const auto wt : TrackList::Get(project).Any< WaveTrack >() ) {
155 if (auto pWaveTrackView =
156 dynamic_cast<WaveTrackView*>( &TrackView::Get(*wt)) ) {
157 for (const auto &pSubView : pWaveTrackView->GetAllSubViews()) {
158 if (const auto sView = dynamic_cast<SpectrumView*>(pSubView.get()))
159 fn( *sView );
160 }
161 }
162 }
163}
164
165std::vector<UIHandlePtr> SpectrumView::DetailedHitTest(
166 const TrackPanelMouseState &state,
167 const AudacityProject *pProject, int currentTool, bool bMultiTool )
168{
169 const auto wt = std::static_pointer_cast< WaveTrack >( FindTrack() );
170 std::vector<UIHandlePtr> results;
171
172#ifdef EXPERIMENTAL_BRUSH_TOOL
173 mOnBrushTool = (currentTool == ToolCodes::brushTool);
174 if(mOnBrushTool){
175 const auto result = BrushHandleHitTest(
176 mBrushHandle, state,
177 pProject, std::static_pointer_cast<SpectrumView>(shared_from_this()),
179 results.push_back(result);
180 return results;
181 }
182#endif
183
185 state, pProject, currentTool, bMultiTool, wt
186 ).second;
187}
188
189void SpectrumView::DoSetMinimized( bool minimized )
190{
191 auto wt = static_cast<WaveTrack*>( FindTrack().get() );
192
193#ifdef EXPERIMENTAL_HALF_WAVE
194 bool bHalfWave;
195 gPrefs->Read(wxT("/GUI/CollapseToHalfWave"), &bHalfWave, false);
196 if( bHalfWave && minimized)
197 {
198 // It is all right to set the top of scale to a huge number,
199 // not knowing the track rate here -- because when retrieving the
200 // value, then we pass in a sample rate and clamp it above to the
201 // Nyquist frequency.
202 constexpr auto max = std::numeric_limits<float>::max();
203 const bool spectrumLinear =
204 (wt->GetSpectrogramSettings().scaleType ==
206 // Zoom out full
207 wt->SetSpectrumBounds( spectrumLinear ? 0.0f : 1.0f, max );
208 }
209#endif
210
211 TrackView::DoSetMinimized( minimized );
212}
213
214auto SpectrumView::SubViewType() const -> const Type &
215{
216 return sType;
217}
218
219std::shared_ptr<TrackVRulerControls> SpectrumView::DoGetVRulerControls()
220{
221 return std::make_shared<SpectrumVRulerControls>( shared_from_this() );
222}
223
224std::shared_ptr<SpectralData> SpectrumView::GetSpectralData(){
225 return mpSpectralData;
226}
227
229 if ( const auto pDest = dynamic_cast< SpectrumView* >( destSubView ) ) {
230 pDest->mpSpectralData = std::make_shared<SpectralData>(mpSpectralData->GetSR());
231 pDest->mpSpectralData->CopyFrom(*mpSpectralData);
232 }
233}
234
235namespace
236{
237
238static inline float findValue
239(const float *spectrum, float bin0, float bin1, unsigned nBins,
240 bool autocorrelation, int gain, int range)
241{
242 float value;
243
244
245#if 0
246 // Averaging method
247 if ((int)(bin1) == (int)(bin0)) {
248 value = spectrum[(int)(bin0)];
249 } else {
250 float binwidth= bin1 - bin0;
251 value = spectrum[(int)(bin0)] * (1.f - bin0 + (int)bin0);
252
253 bin0 = 1 + (int)(bin0);
254 while (bin0 < (int)(bin1)) {
255 value += spectrum[(int)(bin0)];
256 bin0 += 1.0;
257 }
258 // Do not reference past end of freq array.
259 if ((int)(bin1) >= (int)nBins) {
260 bin1 -= 1.0;
261 }
262
263 value += spectrum[(int)(bin1)] * (bin1 - (int)(bin1));
264 value /= binwidth;
265 }
266#else
267 // Maximum method, and no apportionment of any single bins over multiple pixel rows
268 // See Bug971
269 int index, limitIndex;
270 if (autocorrelation) {
271 // bin = 2 * nBins / (nBins - 1 - array_index);
272 // Solve for index
273 index = std::max(0.0f, std::min(float(nBins - 1),
274 (nBins - 1) - (2 * nBins) / (std::max(1.0f, bin0))
275 ));
276 limitIndex = std::max(0.0f, std::min(float(nBins - 1),
277 (nBins - 1) - (2 * nBins) / (std::max(1.0f, bin1))
278 ));
279 }
280 else {
281 index = std::min<int>(nBins - 1, (int)(floor(0.5 + bin0)));
282 limitIndex = std::min<int>(nBins, (int)(floor(0.5 + bin1)));
283 }
284 value = spectrum[index];
285 while (++index < limitIndex)
286 value = std::max(value, spectrum[index]);
287#endif
288 if (!autocorrelation) {
289 // Last step converts dB to a 0.0-1.0 range
290 value = (value + range + gain) / (double)range;
291 }
292 value = std::min(1.0f, std::max(0.0f, value));
293 return value;
294}
295
296// dashCount counts both dashes and the spaces between them.
298ChooseColorSet( float bin0, float bin1, float selBinLo,
299 float selBinCenter, float selBinHi, int dashCount, bool isSpectral )
300{
301 if (!isSpectral)
303 if ((selBinCenter >= 0) && (bin0 <= selBinCenter) &&
304 (selBinCenter < bin1))
306 if ((0 == dashCount % 2) &&
307 (((selBinLo >= 0) && (bin0 <= selBinLo) && ( selBinLo < bin1)) ||
308 ((selBinHi >= 0) && (bin0 <= selBinHi) && ( selBinHi < bin1))))
310 if ((selBinLo < 0 || selBinLo < bin1) && (selBinHi < 0 || selBinHi > bin0))
312
314}
315
317 SampleTrackCache &waveTrackCache,
318 const WaveClip *clip,
319 const wxRect &rect,
320 const std::shared_ptr<SpectralData> &mpSpectralData,
321 bool selected)
322{
323 auto &dc = context.dc;
324 const auto artist = TrackArtist::Get( context );
325 bool onBrushTool = artist->onBrushTool;
326 const auto &selectedRegion = *artist->pSelectedRegion;
327 const auto &zoomInfo = *artist->pZoomInfo;
328
329#ifdef PROFILE_WAVEFORM
330 Profiler profiler;
331#endif
332
333 //If clip is "too small" draw a placeholder instead of
334 //attempting to fit the contents into a few pixels
335 if (!WaveTrackView::ClipDetailsVisible(*clip, zoomInfo, rect))
336 {
337 auto clipRect = ClipParameters::GetClipRect(*clip, zoomInfo, rect);
338 TrackArt::DrawClipFolded(dc, clipRect);
339 return;
340 }
341
342 const auto track =
343 dynamic_cast<const WaveTrack*>(waveTrackCache.GetTrack().get());
344 if (!track)
345 // Leave a blank rectangle.
346 // TODO: rewrite GetSpectrogramSettings and GetSpectrumBounds so they
347 // are not members of WaveTrack, but fetch UI related ClientData
348 // attachments; then this downcast from SampleTrack will not be needed.
349 return;
350
351 const SpectrogramSettings &settings = track->GetSpectrogramSettings();
352 const bool autocorrelation = (settings.algorithm == SpectrogramSettings::algPitchEAC);
353
354 enum { DASH_LENGTH = 10 /* pixels */ };
355
357 true, track, clip, rect, selectedRegion, zoomInfo };
358 const wxRect &hiddenMid = params.hiddenMid;
359 // The "hiddenMid" rect contains the part of the display actually
360 // containing the waveform, as it appears without the fisheye. If it's empty, we're done.
361 if (hiddenMid.width <= 0) {
362 return;
363 }
364
365 const double &t0 = params.t0;
366 const double &tOffset = params.tOffset;
367 const auto &ssel0 = params.ssel0;
368 const auto &ssel1 = params.ssel1;
369 const double &averagePixelsPerSample = params.averagePixelsPerSample;
370 const double &rate = params.rate;
371 const double &hiddenLeftOffset = params.hiddenLeftOffset;
372 const double &leftOffset = params.leftOffset;
373 const wxRect &mid = params.mid;
374
377#ifdef EXPERIMENTAL_SPECTRAL_EDITING
378 freqLo = selectedRegion.f0();
379 freqHi = selectedRegion.f1();
380#endif
381
382 const int &colorScheme = settings.colorScheme;
383 const int &range = settings.range;
384 const int &gain = settings.gain;
385
386#ifdef EXPERIMENTAL_FIND_NOTES
387 const bool &fftFindNotes = settings.fftFindNotes;
388 const double &findNotesMinA = settings.findNotesMinA;
389 const int &numberOfMaxima = settings.numberOfMaxima;
390 const bool &findNotesQuantize = settings.findNotesQuantize;
391#endif
392#ifdef EXPERIMENTAL_FFT_Y_GRID
393 const bool &fftYGrid = settings.fftYGrid;
394#endif
395
396 dc.SetPen(*wxTRANSPARENT_PEN);
397
398 // We draw directly to a bit image in memory,
399 // and then paint this directly to our offscreen
400 // bitmap. Note that this could be optimized even
401 // more, but for now this is not bad. -dmazzoni
402 wxImage image((int)mid.width, (int)mid.height);
403 if (!image.IsOk())
404 return;
405#ifdef EXPERIMENTAL_SPECTROGRAM_OVERLAY
406 image.SetAlpha();
407 unsigned char *alpha = image.GetAlpha();
408#endif
409 unsigned char *data = image.GetData();
410
411 const auto half = settings.GetFFTLength() / 2;
412 const double binUnit = rate / (2 * half);
413 const float *freq = 0;
414 const sampleCount *where = 0;
415 bool updated;
416 {
417 const double pps = averagePixelsPerSample * rate;
418 updated = WaveClipSpectrumCache::Get( *clip ).GetSpectrogram( *clip,
419 waveTrackCache, freq, where,
420 (size_t)hiddenMid.width,
421 t0, pps);
422 }
423 auto nBins = settings.NBins();
424
425 float minFreq, maxFreq;
426 track->GetSpectrumBounds(&minFreq, &maxFreq);
427
428 const SpectrogramSettings::ScaleType scaleType = settings.scaleType;
429
430 // nearest frequency to each pixel row from number scale, for selecting
431 // the desired fft bin(s) for display on that row
432 float *bins = (float*)alloca(sizeof(*bins)*(hiddenMid.height + 1));
433 {
434 const NumberScale numberScale( settings.GetScale( minFreq, maxFreq ) );
435
436 NumberScale::Iterator it = numberScale.begin(mid.height);
437 float nextBin = std::max( 0.0f, std::min( float(nBins - 1),
438 settings.findBin( *it, binUnit ) ) );
439
440 int yy;
441 for (yy = 0; yy < hiddenMid.height; ++yy) {
442 bins[yy] = nextBin;
443 nextBin = std::max( 0.0f, std::min( float(nBins - 1),
444 settings.findBin( *++it, binUnit ) ) );
445 }
446 bins[yy] = nextBin;
447 }
448
449#ifdef EXPERIMENTAL_FFT_Y_GRID
450 const float
451 log2 = logf(2.0f),
452 scale2 = (lmax - lmin) / log2,
453 lmin2 = lmin / log2;
454
455 ArrayOf<bool> yGrid{size_t(mid.height)};
456 for (int yy = 0; yy < mid.height; ++yy) {
457 float n = (float(yy) / mid.height*scale2 - lmin2) * 12;
458 float n2 = (float(yy + 1) / mid.height*scale2 - lmin2) * 12;
459 float f = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n / 12.0f + lmin2);
460 float f2 = float(minFreq) / (fftSkipPoints + 1)*powf(2.0f, n2 / 12.0f + lmin2);
461 n = logf(f / 440) / log2 * 12;
462 n2 = logf(f2 / 440) / log2 * 12;
463 if (floor(n) < floor(n2))
464 yGrid[yy] = true;
465 else
466 yGrid[yy] = false;
467 }
468#endif //EXPERIMENTAL_FFT_Y_GRID
469
470 auto &clipCache = WaveClipSpectrumCache::Get( *clip );
471 if (!updated && clipCache.mSpecPxCache->valid &&
472 ((int)clipCache.mSpecPxCache->len == hiddenMid.height * hiddenMid.width)
473 && scaleType == clipCache.mSpecPxCache->scaleType
474 && gain == clipCache.mSpecPxCache->gain
475 && range == clipCache.mSpecPxCache->range
476 && minFreq == clipCache.mSpecPxCache->minFreq
477 && maxFreq == clipCache.mSpecPxCache->maxFreq
478#ifdef EXPERIMENTAL_FFT_Y_GRID
479 && fftYGrid==fftYGridOld
480#endif //EXPERIMENTAL_FFT_Y_GRID
481#ifdef EXPERIMENTAL_FIND_NOTES
482 && fftFindNotes == artist->fftFindNotesOld
483 && findNotesMinA == artist->findNotesMinAOld
484 && numberOfMaxima == artist->findNotesNOld
485 && findNotesQuantize == artist->findNotesQuantizeOld
486#endif
487 ) {
488 // Wave clip's spectrum cache is up to date,
489 // and so is the spectrum pixel cache
490 }
491 else {
492 // Update the spectrum pixel cache
493 clipCache.mSpecPxCache = std::make_unique<SpecPxCache>(hiddenMid.width * hiddenMid.height);
494 clipCache.mSpecPxCache->valid = true;
495 clipCache.mSpecPxCache->scaleType = scaleType;
496 clipCache.mSpecPxCache->gain = gain;
497 clipCache.mSpecPxCache->range = range;
498 clipCache.mSpecPxCache->minFreq = minFreq;
499 clipCache.mSpecPxCache->maxFreq = maxFreq;
500#ifdef EXPERIMENTAL_FIND_NOTES
501 artist->fftFindNotesOld = fftFindNotes;
502 artist->findNotesMinAOld = findNotesMinA;
503 artist->findNotesNOld = numberOfMaxima;
504 artist->findNotesQuantizeOld = findNotesQuantize;
505#endif
506
507#ifdef EXPERIMENTAL_FIND_NOTES
508 float log2 = logf( 2.0f ),
509 lmin = logf( minFreq ), lmax = logf( maxFreq ), scale = lmax - lmin,
510 lmins = lmin,
511 lmaxs = lmax
512 ;
513#endif //EXPERIMENTAL_FIND_NOTES
514
515#ifdef EXPERIMENTAL_FIND_NOTES
516 int maxima[128];
517 float maxima0[128], maxima1[128];
518 const float
519 f2bin = half / (rate / 2.0f),
520 bin2f = 1.0f / f2bin,
521 minDistance = powf(2.0f, 2.0f / 12.0f),
522 i0 = expf(lmin) / binUnit,
523 i1 = expf(scale + lmin) / binUnit,
524 minColor = 0.0f;
525 const size_t maxTableSize = 1024;
526 ArrayOf<int> indexes{ maxTableSize };
527#endif //EXPERIMENTAL_FIND_NOTES
528
529#ifdef _OPENMP
530#pragma omp parallel for
531#endif
532 for (int xx = 0; xx < hiddenMid.width; ++xx) {
533#ifdef EXPERIMENTAL_FIND_NOTES
534 int maximas = 0;
535 const int x0 = nBins * xx;
536 if (fftFindNotes) {
537 for (int i = maxTableSize - 1; i >= 0; i--)
538 indexes[i] = -1;
539
540 // Build a table of (most) values, put the index in it.
541 for (int i = (int)(i0); i < (int)(i1); i++) {
542 float freqi = freq[x0 + (int)(i)];
543 int value = (int)((freqi + gain + range) / range*(maxTableSize - 1));
544 if (value < 0)
545 value = 0;
546 if (value >= maxTableSize)
547 value = maxTableSize - 1;
548 indexes[value] = i;
549 }
550 // Build from the indices an array of maxima.
551 for (int i = maxTableSize - 1; i >= 0; i--) {
552 int index = indexes[i];
553 if (index >= 0) {
554 float freqi = freq[x0 + index];
555 if (freqi < findNotesMinA)
556 break;
557
558 bool ok = true;
559 for (int m = 0; m < maximas; m++) {
560 // Avoid to store very close maxima.
561 float maxm = maxima[m];
562 if (maxm / index < minDistance && index / maxm < minDistance) {
563 ok = false;
564 break;
565 }
566 }
567 if (ok) {
568 maxima[maximas++] = index;
569 if (maximas >= numberOfMaxima)
570 break;
571 }
572 }
573 }
574
575// The f2pix helper macro converts a frequency into a pixel coordinate.
576#define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*hiddenMid.height
577
578 // Possibly quantize the maxima frequencies and create the pixel block limits.
579 for (int i = 0; i < maximas; i++) {
580 int index = maxima[i];
581 float f = float(index)*bin2f;
582 if (findNotesQuantize)
583 {
584 f = expf((int)(log(f / 440) / log2 * 12 - 0.5) / 12.0f*log2) * 440;
585 maxima[i] = f*f2bin;
586 }
587 float f0 = expf((log(f / 440) / log2 * 24 - 1) / 24.0f*log2) * 440;
588 maxima0[i] = f2pix(f0);
589 float f1 = expf((log(f / 440) / log2 * 24 + 1) / 24.0f*log2) * 440;
590 maxima1[i] = f2pix(f1);
591 }
592 }
593
594 int it = 0;
595 bool inMaximum = false;
596#endif //EXPERIMENTAL_FIND_NOTES
597
598 for (int yy = 0; yy < hiddenMid.height; ++yy) {
599 const float bin = bins[yy];
600 const float nextBin = bins[yy+1];
601
603 const float value = findValue
604 (freq + nBins * xx, bin, nextBin, nBins, autocorrelation, gain, range);
605 clipCache.mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
606 }
607 else {
608 float value;
609
610#ifdef EXPERIMENTAL_FIND_NOTES
611 if (fftFindNotes) {
612 if (it < maximas) {
613 float i0 = maxima0[it];
614 if (yy >= i0)
615 inMaximum = true;
616
617 if (inMaximum) {
618 float i1 = maxima1[it];
619 if (yy + 1 <= i1) {
620 value = findValue(freq + x0, bin, nextBin, nBins, autocorrelation, gain, range);
621 if (value < findNotesMinA)
622 value = minColor;
623 }
624 else {
625 it++;
626 inMaximum = false;
627 value = minColor;
628 }
629 }
630 else {
631 value = minColor;
632 }
633 }
634 else
635 value = minColor;
636 }
637 else
638#endif //EXPERIMENTAL_FIND_NOTES
639 {
640 value = findValue
641 (freq + nBins * xx, bin, nextBin, nBins, autocorrelation, gain, range);
642 }
643 clipCache.mSpecPxCache->values[xx * hiddenMid.height + yy] = value;
644 } // logF
645 } // each yy
646 } // each xx
647 } // updating cache
648
649 float selBinLo = settings.findBin( freqLo, binUnit);
650 float selBinHi = settings.findBin( freqHi, binUnit);
651 float selBinCenter = (freqLo < 0 || freqHi < 0)
652 ? -1
653 : settings.findBin( sqrt(freqLo * freqHi), binUnit );
654
655 const bool isSpectral = settings.SpectralSelectionEnabled();
656 const bool hidden = (ZoomInfo::HIDDEN == zoomInfo.GetFisheyeState());
657 const int begin = hidden
658 ? 0
659 : std::max(0, (int)(zoomInfo.GetFisheyeLeftBoundary(-leftOffset)));
660 const int end = hidden
661 ? 0
662 : std::min(mid.width, (int)(zoomInfo.GetFisheyeRightBoundary(-leftOffset)));
663 const size_t numPixels = std::max(0, end - begin);
664
665 SpecCache specCache;
666
667 // need explicit resize since specCache.where[] accessed before Populate()
668 specCache.Grow(numPixels, settings, -1, t0);
669
670 if (numPixels > 0) {
671 for (int ii = begin; ii < end; ++ii) {
672 const double time = zoomInfo.PositionToTime(ii, -leftOffset) - tOffset;
673 specCache.where[ii - begin] = sampleCount(0.5 + rate * time);
674 }
675 specCache.Populate
676 (settings, waveTrackCache,
677 0, 0, numPixels,
678 clip->GetPlaySamplesCount(),
679 tOffset, rate,
680 0 // FIXME: PRL -- make reassignment work with fisheye
681 );
682 }
683
684 // build color gradient tables (not thread safe)
687
688 // left pixel column of the fisheye
689 int fisheyeLeft = zoomInfo.GetFisheyeLeftBoundary(-leftOffset);
690
691 // Bug 2389 - always draw at least one pixel of selection.
692 int selectedX = zoomInfo.TimeToPosition(selectedRegion.t0(), -leftOffset);
693
694#ifdef _OPENMP
695#pragma omp parallel for
696#endif
697
698 const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
699 int windowSize = mpSpectralData->GetWindowSize();
700 int hopSize = mpSpectralData->GetHopSize();
701 double sr = mpSpectralData->GetSR();
702 auto &dataHistory = mpSpectralData->dataHistory;
703
704 // Lazy way to add all hops and bins required for rendering
705 dataHistory.push_back(mpSpectralData->dataBuffer);
706
707 // Generate combined hops and bins map for rendering
708 std::map<long long, std::set<int>> hopBinMap;
709 for(auto vecIter = dataHistory.begin(); vecIter != dataHistory.end(); ++vecIter){
710 for(const auto &hopMap: *vecIter){
711 for(const auto &binNum: hopMap.second)
712 hopBinMap[hopMap.first].insert(binNum);
713 }
714 }
715
716 // Lambda for converting yy (not mouse coord!) to respective freq. bins
717 auto yyToFreqBin = [&](int yy){
718 const double p = double(yy) / hiddenMid.height;
719 float convertedFreq = numberScale.PositionToValue(p);
720 float convertedFreqBinNum = convertedFreq / (sr / windowSize);
721
722 // By default lrintf will round to nearest by default, rounding to even on tie.
723 // std::round that was used here before rounds halfway cases away from zero.
724 // However, we can probably tolerate rounding issues here, as this will only slightly affect
725 // the visuals.
726 return static_cast<int>(lrintf(convertedFreqBinNum));
727 };
728
729 for (int xx = 0; xx < mid.width; ++xx) {
730 int correctedX = xx + leftOffset - hiddenLeftOffset;
731
732 // in fisheye mode the time scale has changed, so the row values aren't cached
733 // in the loop above, and must be fetched from fft cache
734 float* uncached;
735 if (!zoomInfo.InFisheye(xx, -leftOffset)) {
736 uncached = 0;
737 }
738 else {
739 int specIndex = (xx - fisheyeLeft) * nBins;
740 wxASSERT(specIndex >= 0 && specIndex < (int)specCache.freq.size());
741 uncached = &specCache.freq[specIndex];
742 }
743
744 // zoomInfo must be queried for each column since with fisheye enabled
745 // time between columns is variable
746 auto w0 = sampleCount(0.5 + rate *
747 (zoomInfo.PositionToTime(xx, -leftOffset) - tOffset));
748
749 auto w1 = sampleCount(0.5 + rate *
750 (zoomInfo.PositionToTime(xx+1, -leftOffset) - tOffset));
751
752 bool maybeSelected = ssel0 <= w0 && w1 < ssel1;
753 maybeSelected = maybeSelected || (xx == selectedX);
754
755 // In case the xx matches the hop number, it will be used as iterator for frequency bins
756 std::set<int> *pSelectedBins = nullptr;
757 std::set<int>::iterator freqBinIter;
758 auto advanceFreqBinIter = [&](int nextBinRounded){
759 while (freqBinIter != pSelectedBins->end() &&
760 *freqBinIter < nextBinRounded)
761 ++freqBinIter;
762 };
763
764 bool hitHopNum = false;
765 if (onBrushTool) {
766 int convertedHopNum = (w0.as_long_long() + hopSize / 2) / hopSize;
767 hitHopNum = (hopBinMap.find(convertedHopNum) != hopBinMap.end());
768 if(hitHopNum) {
769 pSelectedBins = &hopBinMap[convertedHopNum];
770 freqBinIter = pSelectedBins->begin();
771 advanceFreqBinIter(yyToFreqBin(0));
772 }
773 }
774
775 for (int yy = 0; yy < hiddenMid.height; ++yy) {
776 if(onBrushTool)
777 maybeSelected = false;
778 const float bin = bins[yy];
779 const float nextBin = bins[yy+1];
780 auto binRounded = yyToFreqBin(yy);
781 auto nextBinRounded = yyToFreqBin(yy + 1);
782
783 if(hitHopNum
784 && freqBinIter != pSelectedBins->end()
785 && binRounded == *freqBinIter)
786 maybeSelected = true;
787
788 if (hitHopNum)
789 advanceFreqBinIter(nextBinRounded);
790
791 // For spectral selection, determine what colour
792 // set to use. We use a darker selection if
793 // in both spectral range and time range.
794
796
797 // If we are in the time selected range, then we may use a different color set.
798 if (maybeSelected) {
799 selected =
800 ChooseColorSet(bin, nextBin, selBinLo, selBinCenter, selBinHi,
801 (xx + leftOffset - hiddenLeftOffset) / DASH_LENGTH, isSpectral);
802 if ( onBrushTool && selected != AColor::ColorGradientUnselected )
803 // use only two sets of colors
805 }
806
807 const float value = uncached
808 ? findValue(uncached, bin, nextBin, nBins, autocorrelation, gain, range)
809 : clipCache.mSpecPxCache->values[correctedX * hiddenMid.height + yy];
810
811 unsigned char rv, gv, bv;
812 GetColorGradient(value, selected, colorScheme, &rv, &gv, &bv);
813
814#ifdef EXPERIMENTAL_FFT_Y_GRID
815 if (fftYGrid && yGrid[yy]) {
816 rv /= 1.1f;
817 gv /= 1.1f;
818 bv /= 1.1f;
819 }
820#endif //EXPERIMENTAL_FFT_Y_GRID
821 int px = ((mid.height - 1 - yy) * mid.width + xx);
822#ifdef EXPERIMENTAL_SPECTROGRAM_OVERLAY
823 // More transparent the closer to zero intensity.
824 alpha[px]= wxMin( 200, (value+0.3) * 500) ;
825#endif
826 px *=3;
827 data[px++] = rv;
828 data[px++] = gv;
829 data[px] = bv;
830 } // each yy
831 } // each xx
832
833 dataHistory.pop_back();
834 wxBitmap converted = wxBitmap(image);
835
836 wxMemoryDC memDC;
837
838 memDC.SelectObject(converted);
839
840 dc.Blit(mid.x, mid.y, mid.width, mid.height, &memDC, 0, 0, wxCOPY, FALSE);
841
842 // Draw clip edges, as also in waveform view, which improves the appearance
843 // of split views
844 {
845 auto clipRect = ClipParameters::GetClipRect(*clip, zoomInfo, rect);
846 TrackArt::DrawClipEdges(dc, clipRect, selected);
847 }
848}
849
850}
851
853 const WaveTrack* track,
854 const WaveClip* selectedClip,
855 const wxRect & rect )
856{
857 const auto artist = TrackArtist::Get( context );
858 const auto &blankSelectedBrush = artist->blankSelectedBrush;
859 const auto &blankBrush = artist->blankBrush;
861 context, rect, track, blankSelectedBrush, blankBrush );
862
863 SampleTrackCache cache(track->SharedPointer<const WaveTrack>());
864 for (const auto &clip: track->GetClips()){
865 DrawClipSpectrum( context, cache, clip.get(), rect,
866 mpSpectralData, clip.get() == selectedClip);
867 }
868
869 DrawBoldBoundaries( context, track, rect );
870}
871
873 TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass )
874{
875 if ( iPass == TrackArtist::PassTracks ) {
876 auto &dc = context.dc;
877 // Update cache for locations, e.g. cutlines and merge points
878 // Bug2588: do this for both channels, even if one is not drawn, so that
879 // cut-line editing (which depends on the locations cache) works properly.
880 // If both channels are visible, we will duplicate this effort, but that
881 // matters little.
882 for( auto channel:
883 TrackList::Channels(static_cast<WaveTrack*>(FindTrack().get())) )
884 channel->UpdateLocationsCache();
885
886 const auto wt = std::static_pointer_cast<const WaveTrack>(
887 FindTrack()->SubstitutePendingChangedTrack());
888
889 const auto artist = TrackArtist::Get( context );
890
891#if defined(__WXMAC__)
892 wxAntialiasMode aamode = dc.GetGraphicsContext()->GetAntialiasMode();
893 dc.GetGraphicsContext()->SetAntialiasMode(wxANTIALIAS_NONE);
894#endif
895
896 auto waveTrackView = GetWaveTrackView().lock();
897 wxASSERT(waveTrackView.use_count());
898
899 auto seletedClip = waveTrackView->GetSelectedClip().lock();
900 DoDraw( context, wt.get(), seletedClip.get(), rect );
901
902#if defined(__WXMAC__)
903 dc.GetGraphicsContext()->SetAntialiasMode(aamode);
904#endif
905 }
906 WaveTrackSubView::Draw( context, rect, iPass );
907}
908
910 []( WaveTrackView &view ){
911 return std::make_shared< SpectrumView >( view );
912 }
913};
914
915// The following attaches the spectrogram settings item to the wave track popup
916// menu. It is appropriate only to spectrum view and so is kept in this
917// source file with the rest of the spectrum view implementation.
918#include "WaveTrackControls.h"
919#include "AudioIOBase.h"
920#include "../../../../Menus.h"
921#include "ProjectHistory.h"
922#include "../../../../RefreshCode.h"
923#include "../../../../prefs/PrefsDialog.h"
924#include "../../../../prefs/SpectrumPrefs.h"
925#include "../../../../widgets/AudacityMessageBox.h"
926#include "../../../../widgets/PopupMenuTable.h"
927
928namespace {
930
933 {
934 static SpectrogramSettingsHandler instance;
935 return instance;
936 }
937
938 void OnSpectrogramSettings(wxCommandEvent &);
939
940 void InitUserData(void *pUserData) override
941 {
942 mpData = static_cast< PlayableTrackControls::InitMenuData* >(pUserData);
943 }
944};
945
946void SpectrogramSettingsHandler::OnSpectrogramSettings(wxCommandEvent &)
947{
948 class ViewSettingsDialog final : public PrefsDialog
949 {
950 public:
951 ViewSettingsDialog(wxWindow *parent, AudacityProject &project,
953 int page)
954 : PrefsDialog(parent, &project, title, factories)
955 , mPage(page)
956 {
957 }
958
959 long GetPreferredPage() override
960 {
961 return mPage;
962 }
963
964 void SavePreferredPage() override
965 {
966 }
967
968 private:
969 const int mPage;
970 };
971
972 auto gAudioIO = AudioIOBase::Get();
973 if (gAudioIO->IsBusy()){
975 XO(
976"To change Spectrogram Settings, stop any\n playing or recording first."),
977 XO("Stop the Audio First"),
978 wxOK | wxICON_EXCLAMATION | wxCENTRE);
979 return;
980 }
981
982 WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
983
984 PrefsPanel::Factories factories;
985 // factories.push_back(WaveformPrefsFactory( pTrack ));
986 factories.push_back(SpectrumPrefsFactory( pTrack ));
987 const int page =
988 // (pTrack->GetDisplay() == WaveTrackViewConstants::Spectrum) ? 1 :
989 0;
990
991 auto title = XO("%s:").Format( pTrack->GetName() );
992 ViewSettingsDialog dialog(
993 mpData->pParent, mpData->project, title, factories, page);
994
995 if (0 != dialog.ShowModal()) {
996 // Redraw
997 AudacityProject *const project = &mpData->project;
998 ProjectHistory::Get( *project ).ModifyState(true);
999 //Bug 1725 Toolbar was left greyed out.
1000 //This solution is overkill, but does fix the problem and is what the
1001 //prefs dialog normally does.
1003 mpData->result = RefreshCode::RefreshAll;
1004 }
1005}
1006
1009 { "SubViews/Extra" },
1010 std::make_unique<PopupMenuSection>( "SpectrogramSettings",
1011 // Conditionally add menu item for settings, if showing spectrum
1012 PopupMenuTable::Computed< WaveTrackPopupMenuTable >(
1015 static const int OnSpectrogramSettingsID =
1017
1018 const auto pTrack = &table.FindWaveTrack();
1019 const auto &view = WaveTrackView::Get( *pTrack );
1020 const auto displays = view.GetDisplays();
1021 bool hasSpectrum = (displays.end() != std::find(
1022 displays.begin(), displays.end(),
1023 WaveTrackSubView::Type{ WaveTrackViewConstants::Spectrum, {} }
1024 ) );
1025 if( hasSpectrum )
1026 // In future, we might move this to the context menu of the
1027 // Spectrum vertical ruler.
1028 // (But the latter won't be satisfactory without a means to
1029 // open that other context menu with keystrokes only, and that
1030 // would require some notion of a focused sub-view.)
1031 return std::make_unique<Entry>( "SpectrogramSettings",
1032 Entry::Item,
1033 OnSpectrogramSettingsID,
1034 XXO("S&pectrogram Settings..."),
1035 (wxCommandEventFunction)
1036 (&SpectrogramSettingsHandler::OnSpectrogramSettings),
1037 SpectrogramSettingsHandler::Instance(),
1038 []( PopupMenuHandler &handler, wxMenu &menu, int id ){
1039 // Bug 1253. Shouldn't open preferences if audio is busy.
1040 // We can't change them on the fly yet anyway.
1041 auto gAudioIO = AudioIOBase::Get();
1042 menu.Enable(id, !gAudioIO->IsBusy());
1043 } );
1044 else
1045 return nullptr;
1046 } ) )
1047};
1048}
1049
1050static bool ShouldCaptureEvent(wxKeyEvent& event, SpectralData *pData)
1051{
1052 const auto keyCode = event.GetKeyCode();
1053 return
1054 (keyCode == WXK_BACK || keyCode == WXK_DELETE ||
1055 keyCode == WXK_NUMPAD_DELETE)
1056 && pData && !pData->dataHistory.empty();
1057}
1058
1060 wxKeyEvent& event, ViewInfo&, wxWindow*, AudacityProject*)
1061{
1062 bool capture = ShouldCaptureEvent(event, mpSpectralData.get());
1063 event.Skip(!capture);
1065}
1066
1067unsigned SpectrumView::KeyDown(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow*, AudacityProject* project)
1068{
1069 bool capture = ShouldCaptureEvent(event, mpSpectralData.get());
1070 event.Skip(!capture);
1071 if (capture && SpectralDataManager::ProcessTracks(*project))
1072 // Not RefreshCell, because there might be effects in multiple tracks
1075}
1076
1078 wxKeyEvent &event, ViewInfo&, wxWindow*, AudacityProject* )
1079{
1080 bool capture = ShouldCaptureEvent(event, mpSpectralData.get());
1081 event.Skip(!capture);
1083}
void GetColorGradient(float value, AColor::ColorGradientChoice selected, int colorScheme, unsigned char *__restrict red, unsigned char *__restrict green, unsigned char *__restrict blue)
Definition: AColor.h:151
wxImage(22, 22)
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
int min(int a, int b)
EffectDistortion::Params params
Definition: Distortion.cpp:83
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
static const auto title
FileConfig * gPrefs
Definition: Prefs.cpp:71
PrefsPanel::Factory SpectrumPrefsFactory(WaveTrack *wt)
static bool ShouldCaptureEvent(wxKeyEvent &event, SpectralData *pData)
static WaveTrackSubView::Type sType
static WaveTrackSubViewType::RegisteredType reg
static UIHandlePtr BrushHandleHitTest(std::weak_ptr< BrushHandle > &holder, const TrackPanelMouseState &st, const AudacityProject *pProject, const std::shared_ptr< SpectrumView > &pTrackView, const std::shared_ptr< SpectralData > &mpData)
static const WaveTrackSubViews::RegisteredFactory key
static Settings & settings()
Definition: TrackInfo.cpp:87
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
WaveTrackPopupMenuTable & GetWaveTrackMenuTable()
static const auto fn
ColorGradientChoice
Definition: AColor.h:28
@ ColorGradientUnselected
Definition: AColor.h:29
@ ColorGradientTimeAndFrequencySelected
Definition: AColor.h:31
@ ColorGradientEdge
Definition: AColor.h:32
@ ColorGradientTimeSelected
Definition: AColor.h:30
static void PreComputeGradient()
Definition: AColor.cpp:721
static bool gradient_inited
Definition: AColor.h:135
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
static AudioIOBase * Get()
Definition: AudioIOBase.cpp:91
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
std::shared_ptr< Track > FindTrack()
static void RebuildAllMenuBars()
Definition: Menus.cpp:687
float PositionToValue(float pp) const
Definition: NumberScale.h:154
Iterator begin(float nPositions) const
Definition: NumberScale.h:231
PopupMenuTableEntry Entry
Dialog that shows the current PrefsPanel in a tabbed divider.
Definition: PrefsDialog.h:35
virtual void SavePreferredPage()=0
PrefsDialog(wxWindow *parent, AudacityProject *pProject, const TranslatableString &titlePrefix=XO("Preferences:"), PrefsPanel::Factories &factories=PrefsPanel::DefaultFactories())
virtual long GetPreferredPage()=0
std::vector< PrefsPanel::PrefsNode > Factories
Definition: PrefsPanel.h:69
A simple profiler to measure the average time lengths that a particular task/function takes....
Definition: Profiler.h:40
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
A short-lived object, during whose lifetime, the contents of the WaveTrack are assumed not to change.
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)
std::vector< float > freq
Definition: SpectrumCache.h:77
std::vector< sampleCount > where
Definition: SpectrumCache.h:78
void Grow(size_t len_, const SpectrogramSettings &settings, double pixelsPerSecond, double start_)
std::vector< HopsAndBinsMap > dataHistory
Definition: SpectrumView.h:44
static bool ProcessTracks(AudacityProject &project)
Spectrogram settings, either for one track or as defaults.
SpectralDataSaver(SpectrumView &view)
void Init(AudacityProject &project, bool clearAll) override
~SpectrumView() override
static void ForAll(AudacityProject &project, std::function< void(SpectrumView &view)> fn)
void DoSetMinimized(bool minimized) override
unsigned CaptureKey(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
void DoDraw(TrackPanelDrawingContext &context, const WaveTrack *track, const WaveClip *selectedClip, const wxRect &rect)
bool IsSpectral() const override
std::shared_ptr< TrackVRulerControls > DoGetVRulerControls() override
std::shared_ptr< SpectralData > mpSpectralData
Definition: SpectrumView.h:137
std::weak_ptr< BrushHandle > mBrushHandle
Definition: SpectrumView.h:133
unsigned KeyDown(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
std::shared_ptr< SpectralData > mpBackupSpectralData
Definition: SpectrumView.h:137
const Type & SubViewType() const override
unsigned Char(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
std::shared_ptr< SpectralData > GetSpectralData()
void CopyToSubView(WaveTrackSubView *destSubView) const override
std::vector< UIHandlePtr > DetailedHitTest(const TrackPanelMouseState &state, const AudacityProject *pProject, int currentTool, bool bMultiTool) override
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
SpectrumView(WaveTrackView &waveTrackView, const SpectrumView &src)=delete
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
std::shared_ptr< Subclass > SharedPointer()
Definition: Track.h:297
wxString GetName() const
Definition: Track.h:466
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1541
virtual void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass)
virtual void DoSetMinimized(bool isMinimized)
Definition: TrackView.cpp:141
static TrackView & Get(Track &)
Definition: TrackView.cpp:69
Holds a msgid for the translation catalog; may also bind format arguments.
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:101
sampleCount GetPlaySamplesCount() const
Definition: WaveClip.cpp:932
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
WaveClipHolders & GetClips()
Definition: WaveTrack.h:329
static void DrawBoldBoundaries(TrackPanelDrawingContext &context, const WaveTrack *track, const wxRect &rect)
std::pair< bool, std::vector< UIHandlePtr > > DoDetailedHitTest(const TrackPanelMouseState &state, const AudacityProject *pProject, int currentTool, bool bMultiTool, const std::shared_ptr< WaveTrack > &wt)
std::weak_ptr< WaveTrackView > GetWaveTrackView() const
static WaveTrackView & Get(WaveTrack &track)
static bool ClipDetailsVisible(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
@ HIDDEN
Definition: ZoomInfo.h:159
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
#define lrintf(flt)
Definition: float_cast.h:170
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
std::unique_ptr< BaseItem > BaseItemPtr
Definition: Registry.h:71
AUDACITY_DLL_API void DrawClipFolded(wxDC &dc, const wxRect &rect)
Definition: TrackArt.cpp:290
AUDACITY_DLL_API void DrawClipEdges(wxDC &dc, const wxRect &clipRect, bool selected=false)
Definition: TrackArt.cpp:253
AUDACITY_DLL_API void DrawBackgroundWithSelection(TrackPanelDrawingContext &context, const wxRect &rect, const Track *track, const wxBrush &selBrush, const wxBrush &unselBrush, bool useSelection=true)
Definition: TrackArt.cpp:419
PopupMenuTable::AttachedItem sAttachment
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)
void DrawClipSpectrum(TrackPanelDrawingContext &context, SampleTrackCache &waveTrackCache, const WaveClip *clip, const wxRect &rect, const std::shared_ptr< SpectralData > &mpSpectralData, bool selected)
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)
WaveTrack & FindWaveTrack() const
void InitUserData(void *pUserData) override
Called before the menu items are appended.