Audacity 3.2.0
SelectHandle.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5SelectHandle.cpp
6
7Paul Licameli split from TrackPanel.cpp
8
9**********************************************************************/
10
11
12#include "SelectHandle.h"
13
14#include "Scrubbing.h"
15#include "ChannelView.h"
16
17#include "AColor.h"
18#include "../../SpectrumAnalyst.h"
19#include "../../LabelTrack.h"
20#include "NumberScale.h"
21#include "Project.h"
22#include "ProjectAudioIO.h"
23#include "ProjectHistory.h"
24#include "../../ProjectSettings.h"
25#include "../../ProjectWindow.h"
26#include "../../RefreshCode.h"
27#include "../../SelectUtilities.h"
28#include "SelectionState.h"
29#include "SyncLock.h"
30#include "../../TrackArtist.h"
31#include "../../TrackPanelAx.h"
32#include "../../TrackPanel.h"
33#include "../../TrackPanelDrawingContext.h"
34#include "../../TrackPanelMouseEvent.h"
35#include "ViewInfo.h"
36#include "WaveClip.h"
37#include "WaveTrack.h"
38#include "../../prefs/SpectrogramSettings.h"
39#include "../../../images/Cursors.h"
40
41// Only for definition of SonifyBeginModifyState:
42//#include "../../NoteTrack.h"
43
44enum {
45 //This constant determines the size of the horizontal region (in pixels) around
46 //the right and left selection bounds that can be used for horizontal selection adjusting
47 //(or, vertical distance around top and bottom bounds in spectrograms,
48 // for vertical selection adjusting)
50
51 // Seems 4 is too small to work at the top. Why?
53};
54
55// #define SPECTRAL_EDITING_ESC_KEY
56
58{
59 return mSelectionStateChanger.get() != NULL;
60}
61
62namespace
63{
65 wxInt64 FrequencyToPosition(const WaveTrack *wt,
66 double frequency,
67 wxInt64 trackTopEdge,
68 int trackHeight)
69 {
70 const auto &settings = SpectrogramSettings::Get(*wt);
71 float minFreq, maxFreq;
72 SpectrogramBounds::Get(*wt).GetBounds(*wt, minFreq, maxFreq);
73 const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
74 const float p = numberScale.ValueToPosition(frequency);
75 return trackTopEdge + wxInt64((1.0 - p) * trackHeight);
76 }
77
81 bool maySnap,
82 wxInt64 mouseYCoordinate,
83 wxInt64 trackTopEdge,
84 int trackHeight)
85 {
86 const double rate = wt->GetRate();
87
88 // Handle snapping
89 if (maySnap &&
90 mouseYCoordinate - trackTopEdge < FREQ_SNAP_DISTANCE)
91 return rate;
92 if (maySnap &&
93 trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE)
94 return -1;
95
96 const auto &settings = SpectrogramSettings::Get(*wt);
97 float minFreq, maxFreq;
99 .GetBounds(*wt, minFreq, maxFreq);
100 const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
101 const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
102 return numberScale.PositionToValue(1.0 - p);
103 }
104
105 template<typename T>
106 inline void SetIfNotNull(T * pValue, const T Value)
107 {
108 if (pValue == NULL)
109 return;
110 *pValue = Value;
111 }
112
113 // This returns true if we're a spectral editing track.
114 inline bool isSpectralSelectionView(const ChannelView *pChannelView) {
115 return
116 pChannelView &&
117 pChannelView->IsSpectral() &&
118 pChannelView->FindTrack() &&
119 pChannelView->FindTrack()->TypeSwitch<bool>(
120 [&](const WaveTrack &wt) {
121 const auto &settings = SpectrogramSettings::Get(wt);
122 return settings.SpectralSelectionEnabled();
123 });
124 }
125
129#ifdef EXPERIMENTAL_SPECTRAL_EDITING
130 SBBottom, SBTop, SBCenter, SBWidth,
131#endif
132 };
133
135 (
136 const double t0, const double t1,
137 const ViewInfo &viewInfo,
138 double selend, bool onlyWithinSnapDistance,
139 wxInt64 *pPixelDist, double *pPinValue)
140 {
141 const wxInt64 posS = viewInfo.TimeToPosition(selend);
142 const wxInt64 pos0 = viewInfo.TimeToPosition(t0);
143 wxInt64 pixelDist = std::abs(posS - pos0);
144 bool chooseLeft = true;
145
146 if (t1<=t0)
147 // Special case when selection is a point, and thus left
148 // and right distances are the same
149 chooseLeft = (selend < t0);
150 else {
151 const wxInt64 pos1 = viewInfo.TimeToPosition(t1);
152 const wxInt64 rightDist = std::abs(posS - pos1);
153 if (rightDist < pixelDist)
154 chooseLeft = false, pixelDist = rightDist;
155 }
156
157 SetIfNotNull(pPixelDist, pixelDist);
158
159 if (onlyWithinSnapDistance &&
160 pixelDist >= SELECTION_RESIZE_REGION) {
161 SetIfNotNull(pPinValue, -1.0);
162 return SBNone;
163 }
164 else if (chooseLeft) {
165 SetIfNotNull(pPinValue, t1);
166 return SBLeft;
167 }
168 else {
169 SetIfNotNull(pPinValue, t0);
170 return SBRight;
171 }
172 }
173
175 const ViewInfo &viewInfo,
176 wxCoord xx, wxCoord yy, const ChannelView *pChannelView,
177 const wxRect &rect,
178 bool mayDragWidth, bool onlyWithinSnapDistance,
179 double *pPinValue = NULL)
180 {
181 // Choose one of four boundaries to adjust, or the center frequency.
182 // May choose frequencies only if in a spectrogram view and
183 // within the time boundaries.
184 // May choose no boundary if onlyWithinSnapDistance is true.
185 // Otherwise choose the eligible boundary nearest the mouse click.
186 const double selend = viewInfo.PositionToTime(xx, rect.x);
187 wxInt64 pixelDist = 0;
188 const double t0 = viewInfo.selectedRegion.t0();
189 const double t1 = viewInfo.selectedRegion.t1();
190
191 SelectionBoundary boundary =
192 ChooseTimeBoundary(t0,t1,viewInfo, selend, onlyWithinSnapDistance,
193 &pixelDist, pPinValue);
194
195#ifdef EXPERIMENTAL_SPECTRAL_EDITING
196 //const double t0 = viewInfo.selectedRegion.t0();
197 //const double t1 = viewInfo.selectedRegion.t1();
198 const double f0 = viewInfo.selectedRegion.f0();
199 const double f1 = viewInfo.selectedRegion.f1();
200 const double fc = viewInfo.selectedRegion.fc();
201 double ratio = 0;
202
203 bool chooseTime = true;
204 bool chooseBottom = true;
205 bool chooseCenter = false;
206 // Consider adjustment of frequencies only if mouse is
207 // within the time boundaries
208 if (!viewInfo.selectedRegion.isPoint() &&
209 t0 <= selend && selend < t1 &&
210 isSpectralSelectionView(pChannelView)) {
211 // Spectral selection track is always wave
212 auto pTrack = pChannelView->FindTrack();
213 const WaveTrack *const wt =
214 static_cast<const WaveTrack*>(pTrack.get());
215 const wxInt64 bottomSel = (f0 >= 0)
216 ? FrequencyToPosition(wt, f0, rect.y, rect.height)
217 : rect.y + rect.height;
218 const wxInt64 topSel = (f1 >= 0)
219 ? FrequencyToPosition(wt, f1, rect.y, rect.height)
220 : rect.y;
221 wxInt64 signedBottomDist = (int)(yy - bottomSel);
222 wxInt64 verticalDist = std::abs(signedBottomDist);
223 if (bottomSel == topSel)
224 // Top and bottom are too close to resolve on screen
225 chooseBottom = (signedBottomDist >= 0);
226 else {
227 const wxInt64 topDist = std::abs((int)(yy - topSel));
228 if (topDist < verticalDist)
229 chooseBottom = false, verticalDist = topDist;
230 }
231 if (fc > 0
232#ifdef SPECTRAL_EDITING_ESC_KEY
233 && mayDragWidth
234#endif
235 ) {
236 const wxInt64 centerSel =
237 FrequencyToPosition(wt, fc, rect.y, rect.height);
238 const wxInt64 centerDist = abs((int)(yy - centerSel));
239 if (centerDist < verticalDist)
240 chooseCenter = true, verticalDist = centerDist,
241 ratio = f1 / fc;
242 }
243 if (verticalDist >= 0 &&
244 verticalDist < pixelDist) {
245 pixelDist = verticalDist;
246 chooseTime = false;
247 }
248 }
249
250 if (!chooseTime) {
251 // PRL: Seems I need a larger tolerance to make snapping work
252 // at top of track, not sure why
253 if (onlyWithinSnapDistance &&
254 pixelDist >= FREQ_SNAP_DISTANCE) {
255 SetIfNotNull(pPinValue, -1.0);
256 return SBNone;
257 }
258 else if (chooseCenter) {
259 SetIfNotNull(pPinValue, ratio);
260 return SBCenter;
261 }
262 else if (mayDragWidth && fc > 0) {
263 SetIfNotNull(pPinValue, fc);
264 return SBWidth;
265 }
266 else if (chooseBottom) {
267 SetIfNotNull(pPinValue, f1);
268 return SBBottom;
269 }
270 else {
271 SetIfNotNull(pPinValue, f0);
272 return SBTop;
273 }
274 }
275 else
276#endif
277 {
278 return boundary;
279 }
280 }
281
282 wxCursor *SelectCursor()
283 {
284 static auto selectCursor =
285 ::MakeCursor(wxCURSOR_IBEAM, IBeamCursorXpm, 17, 16);
286 return &*selectCursor;
287 }
288
289 wxCursor *EnvelopeCursor()
290 {
291 // This one doubles as the center frequency cursor for spectral selection:
292 static auto envelopeCursor =
293 ::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
294 return &*envelopeCursor;
295 }
296
298 (SelectionBoundary boundary, bool frequencySnapping,
299 TranslatableString &tip, wxCursor *&pCursor)
300 {
301 static wxCursor adjustLeftSelectionCursor{ wxCURSOR_POINT_LEFT };
302 static wxCursor adjustRightSelectionCursor{ wxCURSOR_POINT_RIGHT };
303
304 static auto bottomFrequencyCursor =
305 ::MakeCursor(wxCURSOR_ARROW, BottomFrequencyCursorXpm, 16, 16);
306 static auto topFrequencyCursor =
307 ::MakeCursor(wxCURSOR_ARROW, TopFrequencyCursorXpm, 16, 16);
308 static auto bandWidthCursor =
309 ::MakeCursor(wxCURSOR_ARROW, BandWidthCursorXpm, 16, 16);
310
311 switch (boundary) {
312 case SBNone:
313 pCursor = SelectCursor();
314 break;
315 case SBLeft:
316 tip = XO("Click and drag to move left selection boundary.");
317 pCursor = &adjustLeftSelectionCursor;
318 break;
319 case SBRight:
320 tip = XO("Click and drag to move right selection boundary.");
321 pCursor = &adjustRightSelectionCursor;
322 break;
323#ifdef EXPERIMENTAL_SPECTRAL_EDITING
324 case SBBottom:
325 tip = XO("Click and drag to move bottom selection frequency.");
326 pCursor = &*bottomFrequencyCursor;
327 break;
328 case SBTop:
329 tip = XO("Click and drag to move top selection frequency.");
330 pCursor = &*topFrequencyCursor;
331 break;
332 case SBCenter:
333 {
334#ifndef SPECTRAL_EDITING_ESC_KEY
335 tip =
336 frequencySnapping ?
337 XO("Click and drag to move center selection frequency to a spectral peak.") :
338 XO("Click and drag to move center selection frequency.");
339
340#else
341 shiftDown;
342
343 tip =
344 XO("Click and drag to move center selection frequency.");
345
346#endif
347
348 pCursor = EnvelopeCursor();
349 }
350 break;
351 case SBWidth:
352 tip = XO("Click and drag to adjust frequency bandwidth.");
353 pCursor = &*bandWidthCursor;
354 break;
355#endif
356 default:
357 wxASSERT(false);
358 } // switch
359 // Falls through the switch if there was no boundary found.
360 }
361}
362
364(std::weak_ptr<SelectHandle> &holder,
365 const TrackPanelMouseState &st, const AudacityProject *pProject,
366 const std::shared_ptr<ChannelView> &pChannelView)
367{
368 // This handle is a little special because there may be some state to
369 // preserve during movement before the click.
370 auto old = holder.lock();
371 bool oldUseSnap = true;
372 if (old) {
373 // It should not have started listening to timer events
374 if( old->mTimerHandler ) {
375 wxASSERT(false);
376 // Handle this eventuality anyway, don't leave a dangling back-pointer
377 // in the attached event handler.
378 old->mTimerHandler.reset();
379 }
380 oldUseSnap = old->mUseSnap;
381 }
382
383 const auto &viewInfo = ViewInfo::Get( *pProject );
384 auto result = std::make_shared<SelectHandle>(
385 pChannelView, oldUseSnap, TrackList::Get(*pProject), st, viewInfo);
386
387 result = AssignUIHandlePtr(holder, result);
388
389 //Make sure we are within the selected track
390 // Adjusting the selection edges can be turned off in
391 // the preferences...
392 auto pTrack = pChannelView->FindTrack();
393 if (!pTrack->GetSelected() || !viewInfo.bAdjustSelectionEdges)
394 {
395 return result;
396 }
397
398 {
399 const wxRect &rect = st.rect;
400 wxInt64 leftSel = viewInfo.TimeToPosition(viewInfo.selectedRegion.t0(), rect.x);
401 wxInt64 rightSel = viewInfo.TimeToPosition(viewInfo.selectedRegion.t1(), rect.x);
402 // Something is wrong if right edge comes before left edge
403 wxASSERT(!(rightSel < leftSel));
404 static_cast<void>(leftSel); // Suppress unused variable warnings if not in debug-mode
405 static_cast<void>(rightSel);
406 }
407
408 return result;
409}
410
412(const SelectHandle &oldState, const SelectHandle &newState)
413{
414 auto useSnap = oldState.mUseSnap;
415 // This is guaranteed when constructing the NEW handle:
416 wxASSERT( useSnap == newState.mUseSnap );
417 if (!useSnap)
418 return 0;
419
420 auto &oldSnapState = oldState.mSnapStart;
421 auto &newSnapState = newState.mSnapStart;
422 if ( oldSnapState.Snapped() == newSnapState.Snapped() &&
423 (!oldSnapState.Snapped() ||
424 oldSnapState.outCoord == newSnapState.outCoord) )
425 return 0;
426
428}
429
431 const std::shared_ptr<ChannelView> &pChannelView, bool useSnap,
432 const TrackList &trackList,
433 const TrackPanelMouseState &st, const ViewInfo &viewInfo
434) : mpView{ pChannelView }
435 // Selection dragging can snap to play region boundaries
436 , mSnapManager{ std::make_shared<SnapManager>(
437 *trackList.GetOwner(), trackList, viewInfo, SnapPointArray{
438 SnapPoint{ viewInfo.playRegion.GetLastActiveStart() },
439 SnapPoint{ viewInfo.playRegion.GetLastActiveEnd() },
440 } ) }
441{
442 const wxMouseState &state = st.state;
443 mRect = st.rect;
444
445 auto time = std::max(0.0, viewInfo.PositionToTime(state.m_x, mRect.x));
446 auto pTrack = pChannelView->FindTrack();
447 mSnapStart = mSnapManager->Snap(pTrack.get(), time, false);
450 else
451 mSnapStart.outCoord = -1;
452
453 mUseSnap = useSnap;
454}
455
457{
458}
459
460namespace {
461 // Is the distance between A and B less than D?
462 template < class A, class B, class DIST > bool within(A a, B b, DIST d)
463 {
464 return (a > b - d) && (a < b + d);
465 }
466
467 inline double findMaxRatio(double center, double rate)
468 {
469 const double minFrequency = 1.0;
470 const double maxFrequency = (rate / 2.0);
471 const double frequency =
472 std::min(maxFrequency,
473 std::max(minFrequency, center));
474 return
475 std::min(frequency / minFrequency, maxFrequency / frequency);
476 }
477}
478
480{
481 SetUseSnap(true, project);
482}
483
485{
486 mUseSnap = use;
487
488 bool hasSnap = HasSnap();
489 if (hasSnap)
490 // Repaint to turn the snap lines on or off
492
493 if (IsClicked()) {
494 // Readjust the moving selection end
498 nullptr);
499 }
500}
501
503{
504 return
505 (IsClicked() ? mSnapEnd : mSnapStart).snappedPoint;
506}
507
509{
510 return HasSnap() && mUseSnap;
511}
512
514{
516 SetUseSnap(false, project);
517 return true;
518 }
519 return false;
520}
521
523 const TrackPanelMouseEvent &evt, AudacityProject *pProject)
524{
527
528 using namespace RefreshCode;
529
530 const auto pView = mpView.lock();
531 if ( !pView )
532 return Cancelled;
533
534 wxMouseEvent &event = evt.event;
535 auto &trackList = TrackList::Get(*pProject);
536 const auto sTrack = trackList.Lock(FindTrack());
537 const auto pTrack = sTrack.get();
538 const auto pLeader = *trackList.Find(pTrack);
539 auto &trackPanel = TrackPanel::Get(*pProject);
540 auto &viewInfo = ViewInfo::Get(*pProject);
541
542 mMostRecentX = event.m_x;
543 mMostRecentY = event.m_y;
544
545 bool selectChange = (
546 event.LeftDown() &&
547 event.ControlDown() &&
548 pTrack->TypeSwitch<bool>( [&](LabelTrack &){
549 // We should reach this, only in default of other hits on glyphs or
550 // text boxes.
551 bool bShift = event.ShiftDown();
552 bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
554 *pProject, *pTrack, bShift, true, !unsafe);
555 return true;
556 } )
557 );
558 if ( selectChange )
559 // Do not start a drag
560 return RefreshAll | Cancelled;
561
562 auto &selectionState = SelectionState::Get( *pProject );
563 if (event.LeftDClick() && !event.ShiftDown()) {
564 // Deselect all other tracks and select this one.
565 selectionState.SelectNone(trackList);
566
567 if (pLeader)
568 selectionState.SelectTrack(*pLeader, true, true);
569
570 // Default behavior: select whole track
572 viewInfo, *pLeader, SyncLockState::Get(*pProject).IsSyncLocked());
573
574 // Special case: if we're over a clip in a WaveTrack,
575 // select just that clip
576 pTrack->TypeSwitch( [&] ( WaveTrack &wt ) {
577 auto time = viewInfo.PositionToTime(event.m_x, mRect.x);
578 WaveClip *const selectedClip = wt.GetClipAtTime(time);
579 if (selectedClip) {
580 viewInfo.selectedRegion.setTimes(
581 selectedClip->GetPlayStartTime(), selectedClip->GetPlayEndTime());
582 }
583 } );
584
585 ProjectHistory::Get( *pProject ).ModifyState(false);
586
587 // Do not start a drag
588 return RefreshAll | Cancelled;
589 }
590 else if (!event.LeftDown())
591 return Cancelled;
592
593 mInitialSelection = viewInfo.selectedRegion;
594
596 std::make_shared<SelectionStateChanger>(selectionState, trackList);
597
599
600 bool bShiftDown = event.ShiftDown();
601 bool bCtrlDown = event.ControlDown();
602
604 auto xx = viewInfo.TimeToPosition(mSelStart, mRect.x);
605
606 // I. Shift-click adjusts an existing selection
607 if (bShiftDown || bCtrlDown) {
608 if (bShiftDown)
609 selectionState.ChangeSelectionOnShiftClick(trackList, *pTrack);
610 if( bCtrlDown ){
611 //Commented out bIsSelected toggles, as in Track Control Panel.
612 //bool bIsSelected = pTrack->GetSelected();
613 //Actual bIsSelected will always add.
614 bool bIsSelected = false;
615 // Don't toggle away the last selected track.
616 if (!bIsSelected || trackPanel.GetSelectedTrackCount() > 1)
617 if (pLeader)
618 selectionState.SelectTrack(*pLeader, !bIsSelected, true);
619 }
620
621 double value;
622 // Shift-click, choose closest boundary
623 SelectionBoundary boundary =
624 ChooseBoundary(viewInfo, xx, event.m_y,
625 pView.get(), mRect, false, false, &value);
626 mSelectionBoundary = boundary;
627 switch (boundary) {
628 case SBLeft:
629 case SBRight:
630 {
631#ifdef EXPERIMENTAL_SPECTRAL_EDITING
632 // If drag starts, change time selection only
633 // (also exit frequency snapping)
634 mFreqSelMode = FREQ_SEL_INVALID;
635#endif
636 mSelStartValid = true;
637 mSelStart = value;
639 AdjustSelection(pProject, viewInfo, event.m_x, mRect.x, pTrack);
640 break;
641 }
642#ifdef EXPERIMENTAL_SPECTRAL_EDITING
643 case SBBottom:
644 case SBTop:
645 {
646 mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
647 mFreqSelPin = value;
648 mFreqSelMode =
649 (boundary == SBBottom)
651
652 // Drag frequency only, not time:
653 mSelStartValid = false;
655 static_cast<WaveTrack*>(pTrack),
656 viewInfo, event.m_y, mRect.y, mRect.height);
657 break;
658 }
659 case SBCenter:
660 {
661 const auto wt = static_cast<const WaveTrack*>(pTrack);
662 HandleCenterFrequencyClick(viewInfo, true, wt, value);
663 break;
664 }
665#endif
666 default:
667 wxASSERT(false);
668 };
669
670 // For persistence of the selection change:
671 ProjectHistory::Get( *pProject ).ModifyState(false);
672
673 // Get timer events so we can auto-scroll
674 Connect(pProject);
675
676 // Full refresh since the label area may need to indicate
677 // newly selected tracks.
678 return RefreshAll;
679 }
680
681 // II. Unmodified click starts a NEW selection
682
683 //Make sure you are within the selected track
684 bool startNewSelection = true;
685 if (pTrack && pTrack->GetSelected()) {
686 // Adjusting selection edges can be turned off in the
687 // preferences now
688 if (viewInfo.bAdjustSelectionEdges) {
689#ifdef EXPERIMENTAL_SPECTRAL_EDITING
690 if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
691 isSpectralSelectionView(pView.get())) {
692 // This code is no longer reachable, but it had a place in the
693 // spectral selection prototype. It used to be that you could be
694 // in a center-frequency-snapping mode that was not a mouse drag
695 // but responded to mouse movements. Click exited that and dragged
696 // width instead. PRL.
697
698 // Ignore whether we are inside the time selection.
699 // Exit center-snapping, start dragging the width.
700 mFreqSelMode = FREQ_SEL_PINNED_CENTER;
701 mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
702 mFreqSelPin = viewInfo.selectedRegion.fc();
703 // Do not adjust time boundaries
704 mSelStartValid = false;
706 static_cast<WaveTrack*>(pTrack),
707 viewInfo, event.m_y, mRect.y, mRect.height);
708 // For persistence of the selection change:
709 ProjectHistory::Get( *pProject ).ModifyState(false);
710 mSelectionBoundary = SBWidth;
711 return RefreshNone;
712 }
713 else
714#endif
715 {
716 // Not shift-down, choose boundary only within snapping
717 double value;
718 SelectionBoundary boundary =
719 ChooseBoundary(viewInfo, xx, event.m_y,
720 pView.get(), mRect, true, true, &value);
721 mSelectionBoundary = boundary;
722 switch (boundary) {
723 case SBNone:
724 // startNewSelection remains true
725 break;
726 case SBLeft:
727 case SBRight:
728 startNewSelection = false;
729#ifdef EXPERIMENTAL_SPECTRAL_EDITING
730 // Disable frequency selection
731 mFreqSelMode = FREQ_SEL_INVALID;
732#endif
733 mSelStartValid = true;
734 mSelStart = value;
736 break;
737#ifdef EXPERIMENTAL_SPECTRAL_EDITING
738 case SBBottom:
739 case SBTop:
740 case SBWidth:
741 startNewSelection = false;
742 // Disable time selection
743 mSelStartValid = false;
744 mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
745 mFreqSelPin = value;
746 mFreqSelMode =
747 (boundary == SBWidth) ? FREQ_SEL_PINNED_CENTER :
748 (boundary == SBBottom) ? FREQ_SEL_BOTTOM_FREE :
750 break;
751 case SBCenter:
752 {
753 const auto wt = static_cast<const WaveTrack*>(pTrack);
754 HandleCenterFrequencyClick(viewInfo, false, wt, value);
755 startNewSelection = false;
756 break;
757 }
758#endif
759 default:
760 wxASSERT(false);
761 }
762 }
763 } // bAdjustSelectionEdges
764 }
765
766 // III. Common case for starting a NEW selection
767
768 if (startNewSelection) {
769 // If we didn't move a selection boundary, start a NEW selection
770 selectionState.SelectNone(trackList);
771#ifdef EXPERIMENTAL_SPECTRAL_EDITING
772 StartFreqSelection (viewInfo, event.m_y, mRect.y, mRect.height,
773 pView.get());
774#endif
775 StartSelection(pProject);
776 if (pLeader)
777 selectionState.SelectTrack(*pLeader, true, true);
778 TrackFocus::Get(*pProject).Set(pTrack);
779
780 Connect(pProject);
781 return RefreshAll;
782 }
783 else {
784 Connect(pProject);
785 return RefreshAll;
786 }
787}
788
790(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
791{
792 using namespace RefreshCode;
793
794 const auto pView = mpView.lock();
795 if ( !pView )
796 return Cancelled;
797
798 auto &viewInfo = ViewInfo::Get( *pProject );
799 const wxMouseEvent &event = evt.event;
800
801 int x = mAutoScrolling ? mMostRecentX : event.m_x;
802 int y = mAutoScrolling ? mMostRecentY : event.m_y;
803 mMostRecentX = x;
804 mMostRecentY = y;
805
809
810 // Fuhggeddaboudit if we're not dragging and not autoscrolling.
811 if (!event.Dragging() && !mAutoScrolling)
812 return RefreshNone;
813
814 if (event.CmdDown()) {
815 // Ctrl-drag has no meaning, fuhggeddaboudit
816 // JKC YES it has meaning.
817 //return RefreshNone;
818 }
819
820 // Also fuhggeddaboudit if not in a track.
821 auto pTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
822 if (!pTrack)
823 return RefreshNone;
824
825 // JKC: Logic to prevent a selection smaller than 5 pixels to
826 // prevent accidental dragging when selecting.
827 // (if user really wants a tiny selection, they should zoom in).
828 // Can someone make this value of '5' configurable in
829 // preferences?
830 enum { minimumSizedSelection = 5 }; //measured in pixels
831
832 // Might be dragging frequency bounds only, test
833 if (mSelStartValid) {
834 wxInt64 SelStart = viewInfo.TimeToPosition(mSelStart, mRect.x); //cvt time to pixels.
835 // Abandon this drag if selecting < 5 pixels.
836 if (wxLongLong(SelStart - x).Abs() < minimumSizedSelection)
837 return RefreshNone;
838 }
839
840 if (evt.pCell) {
841 if ( auto clickedTrack =
842 static_cast<CommonTrackPanelCell*>(evt.pCell.get())->FindTrack() ) {
843 // Handle which tracks are selected
844 Track *sTrack = pTrack.get();
845 Track *eTrack = clickedTrack.get();
846 auto &trackList = TrackList::Get( *pProject );
847 if ( sTrack && eTrack && !event.ControlDown() ) {
848 auto &selectionState = SelectionState::Get( *pProject );
849 selectionState.SelectRangeOfTracks( trackList, *sTrack, *eTrack );
850 }
851
852 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
853 #ifndef SPECTRAL_EDITING_ESC_KEY
854 if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
855 !viewInfo.selectedRegion.isPoint())
857 (pProject, viewInfo, y, mRect.y, mRect.height, pView.get());
858 else
859 #endif
860 if ( TrackList::Get( *pProject ).Lock(mFreqSelTrack) == pTrack )
862 static_cast<WaveTrack*>(pTrack.get()),
863 viewInfo, y, mRect.y, mRect.height);
864 #endif
865
866 AdjustSelection(pProject, viewInfo, x, mRect.x, clickedTrack.get());
867 }
868 }
869
870 return RefreshNone
871
872 // If scrubbing does not use the helper poller thread, then
873 // don't refresh at every mouse event, because it slows down seek-scrub.
874 // Instead, let OnTimer do it, which is often enough.
875 // And even if scrubbing does use the thread, then skipping refresh does not
876 // bring that advantage, but it is probably still a good idea anyway.
877
878 // | UpdateSelection
879
880 ;
881}
882
884(const TrackPanelMouseState &st, AudacityProject *pProject)
885{
886 if (!HasSnap() && !mUseSnap)
887 // Moved out of snapping; revert to un-escaped state
888 mUseSnap = true;
889
890 const auto pView = mpView.lock();
891 if ( !pView )
892 return {};
893
894 auto pTrack = FindTrack().lock();
895 if (!pTrack)
896 return {};
897
899 wxCursor *pCursor = SelectCursor();
900 if ( IsClicked() )
901 // Use same cursor as at the click
904 (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER),
905 tip, pCursor);
906 else {
907 // Choose one of many cursors for mouse-over
908
909 auto &viewInfo = ViewInfo::Get( *pProject );
910
911 auto &state = st.state;
913 auto xx = viewInfo.TimeToPosition(time, mRect.x);
914
915 const bool bMultiToolMode =
917
918 //In Multi-tool mode, give multitool prompt if no-special-hit.
919 if (bMultiToolMode) {
920 // Look up the current key binding for Preferences.
921 // (Don't assume it's the default!)
922 auto keyStr =
923 CommandManager::Get( *pProject ).GetKeyFromName(wxT("Preferences"))
924 .Display( true );
925 if (keyStr.empty())
926 // No keyboard preference defined for opening Preferences dialog
927 /* i18n-hint: These are the names of a menu and a command in that menu */
928 keyStr = _("Edit, Preferences...");
929
930 /* i18n-hint: %s is usually replaced by "Ctrl+P" for Windows/Linux, "Command+," for Mac */
931 tip = XO("Multi-Tool Mode: %s for Mouse and Keyboard Preferences.")
932 .Format( keyStr );
933 // Later in this function we may point to some other string instead.
934 if (!pTrack->GetSelected() ||
935 !viewInfo.bAdjustSelectionEdges)
936 ;
937 else {
938 const wxRect &rect = st.rect;
939 const bool bShiftDown = state.ShiftDown();
940 const bool bCtrlDown = state.ControlDown();
941 const bool bModifierDown = bShiftDown || bCtrlDown;
942
943 // If not shift-down and not snapping center, then
944 // choose boundaries only in snapping tolerance,
945 // and may choose center.
946 SelectionBoundary boundary =
947 ChooseBoundary(viewInfo, xx, state.m_y,
948 pView.get(), rect, !bModifierDown, !bModifierDown);
949
950 SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
951 }
952 }
953
954#if 0
955 // This is a vestige of an idea in the prototype version.
956 // Center would snap without mouse button down, click would pin the center
957 // and drag width.
958#ifdef EXPERIMENTAL_SPECTRAL_EDITING
959 if ((mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) &&
961 // Not shift-down, but center frequency snapping toggle is on
962 tip = XO("Click and drag to set frequency bandwidth.");
963 pCursor = &*envelopeCursor;
964 return {};
965 }
966#endif
967#endif
968
969 if (!pTrack->GetSelected() || !viewInfo.bAdjustSelectionEdges)
970 ;
971 else {
972 const wxRect &rect = st.rect;
973 const bool bShiftDown = state.ShiftDown();
974 const bool bCtrlDown = state.ControlDown();
975 const bool bModifierDown = bShiftDown || bCtrlDown;
977 viewInfo, xx, state.m_y,
978 pView.get(), rect, !bModifierDown, !bModifierDown);
979 SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
980 }
981 }
982 if (tip.empty()) {
983 tip = XO("Click and drag to select audio");
984 }
985 if (HasEscape(pProject) && mUseSnap) {
986 tip.Join(
987/* i18n-hint: "Snapping" means automatic alignment of selection edges to any nearby label or clip boundaries */
988 XO("(snapping)"), wxT(" ")
989 );
990 }
991 return { tip, pCursor };
992}
993
995(const TrackPanelMouseEvent &, AudacityProject *pProject,
996 wxWindow *)
997{
998 using namespace RefreshCode;
999 ProjectHistory::Get( *pProject ).ModifyState(false);
1000 mFrequencySnapper.reset();
1001 mSnapManager.reset();
1003 mSelectionStateChanger->Commit();
1004 mSelectionStateChanger.reset();
1005 }
1006
1007 if (mUseSnap && (mSnapStart.outCoord != -1 || mSnapEnd.outCoord != -1))
1008 return RefreshAll;
1009 else
1010 return RefreshNone;
1011}
1012
1014{
1015 mSelectionStateChanger.reset();
1017
1019}
1020
1022 TrackPanelDrawingContext &context,
1023 const wxRect &rect, unsigned iPass )
1024{
1025 if ( iPass == TrackArtist::PassSnapping ) {
1026 auto &dc = context.dc;
1027 // Draw snap guidelines if we have any
1028 if ( mSnapManager ) {
1029 auto coord1 = (mUseSnap || IsClicked()) ? mSnapStart.outCoord : -1;
1030 auto coord2 = (!mUseSnap || !IsClicked()) ? -1 : mSnapEnd.outCoord;
1031 mSnapManager->Draw( &dc, coord1, coord2 );
1032 }
1033 }
1034}
1035
1038 const wxRect &rect, const wxRect &panelRect, unsigned iPass )
1039{
1040 if ( iPass == TrackArtist::PassSnapping )
1041 return MaximizeHeight( rect, panelRect );
1042 else
1043 return rect;
1044}
1045
1046std::weak_ptr<Track> SelectHandle::FindTrack()
1047{
1048 auto pView = mpView.lock();
1049 if (!pView)
1050 return {};
1051 else
1052 return pView->FindTrack();
1053}
1054
1056{
1057 mTimerHandler = std::make_shared<TimerHandler>( this, pProject );
1058}
1059
1061{
1062public:
1064 : mParent{ pParent }
1065 , mConnectedProject{ pProject }
1066 {
1070 }
1071
1072 // Receives timer event notifications, to implement auto-scroll
1074
1075private:
1079};
1080
1082{
1083 // AS: If the user is dragging the mouse and there is a track that
1084 // has captured the mouse, then scroll the screen, as necessary.
1085
1087
1088 // DM: If we're "autoscrolling" (which means that we're scrolling
1089 // because the user dragged from inside to outside the window,
1090 // not because the user clicked in the scroll bar), then
1091 // the selection code needs to be handled slightly differently.
1092 // We set this flag ("mAutoScrolling") to tell the selecting
1093 // code that we didn't get here as a result of a mouse event,
1094 // and therefore it should ignore the event,
1095 // and instead use the last known mouse position. Setting
1096 // this flag also causes the Mac to redraw immediately rather
1097 // than waiting for the next update event; this makes scrolling
1098 // smoother on MacOS 9.
1099
1100 const auto project = mConnectedProject;
1101 const auto &trackPanel = TrackPanel::Get( *project );
1102 auto &window = ProjectWindow::Get( *project );
1103 if (mParent->mMostRecentX >= mParent->mRect.x + mParent->mRect.width) {
1104 mParent->mAutoScrolling = true;
1105 window.TP_ScrollRight();
1106 }
1107 else if (mParent->mMostRecentX < mParent->mRect.x) {
1108 mParent->mAutoScrolling = true;
1109 window.TP_ScrollLeft();
1110 }
1111 else {
1112 // Bug1387: enable autoscroll during drag, if the pointer is at either
1113 // extreme x coordinate of the screen, even if that is still within the
1114 // track area.
1115
1116 int xx = mParent->mMostRecentX, yy = 0;
1117 trackPanel.ClientToScreen(&xx, &yy);
1118 if (xx == 0) {
1119 mParent->mAutoScrolling = true;
1120 window.TP_ScrollLeft();
1121 }
1122 else {
1123 int width, height;
1124 ::wxDisplaySize(&width, &height);
1125 if (xx == width - 1) {
1126 mParent->mAutoScrolling = true;
1127 window.TP_ScrollRight();
1128 }
1129 }
1130 }
1131
1132 auto pTrack = mParent->FindTrack().lock(); // TrackList::Lock() ?
1133 if (mParent->mAutoScrolling && pTrack) {
1134 // AS: To keep the selection working properly as we scroll,
1135 // we fake a mouse event (remember, this method is called
1136 // from a timer tick).
1137
1138 // AS: For some reason, GCC won't let us pass this directly.
1139 wxMouseEvent evt(wxEVT_MOTION);
1140 const auto size = trackPanel.GetSize();
1141 mParent->Drag(
1143 evt, mParent->mRect, size,
1144 ChannelView::Get(*pTrack->GetChannel(0)).shared_from_this() },
1145 project
1146 );
1147 mParent->mAutoScrolling = false;
1148 TrackPanel::Get( *mConnectedProject ).Refresh(false);
1149 }
1150}
1151
1154{
1155 auto &viewInfo = ViewInfo::Get( *pProject );
1156 mSelStartValid = true;
1157
1158 viewInfo.selectedRegion.setTimes(mSelStart, mSelStart);
1159
1160 // PRL: commented out the Sonify stuff with the TrackPanel refactor.
1161 // It was no-op anyway.
1162 //SonifyBeginModifyState();
1163 ProjectHistory::Get( *pProject ).ModifyState(false);
1164 //SonifyEndModifyState();
1165}
1166
1169(AudacityProject *pProject,
1170 ViewInfo &viewInfo, int mouseXCoordinate, int trackLeftEdge,
1171 Track *track)
1172{
1173 if (!mSelStartValid)
1174 // Must be dragging frequency bounds only.
1175 return;
1176
1177 double selend =
1178 std::max(0.0, viewInfo.PositionToTime(mouseXCoordinate, trackLeftEdge));
1179 double origSelend = selend;
1180
1181 auto pTrack = Track::SharedPointer( track );
1182 if (!pTrack)
1183 pTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
1184
1185 if (pTrack && mSnapManager.get()) {
1186 bool rightEdge = (selend > mSelStart);
1187 mSnapEnd = mSnapManager->Snap(pTrack.get(), selend, rightEdge);
1188 if (mSnapEnd.Snapped()) {
1189 if (mUseSnap)
1190 selend = mSnapEnd.outTime;
1192 mSnapEnd.outCoord += trackLeftEdge;
1193 }
1195 mSnapEnd.outCoord = -1;
1196
1197 // Check if selection endpoints are too close together to snap (unless
1198 // using snap-to-time -- then we always accept the snap results)
1199 if (mSnapStart.outCoord >= 0 &&
1200 mSnapEnd.outCoord >= 0 &&
1201 std::abs(mSnapStart.outCoord - mSnapEnd.outCoord) < 3) {
1203 selend = origSelend;
1204 mSnapEnd.outCoord = -1;
1205 }
1206 }
1207 AssignSelection(viewInfo, selend, pTrack.get());
1208}
1209
1211(ViewInfo &viewInfo, double selend, Track *pTrack)
1212{
1213 double sel0, sel1;
1214 if (mSelStart < selend) {
1215 sel0 = mSelStart;
1216 sel1 = selend;
1217 }
1218 else {
1219 sel1 = mSelStart;
1220 sel0 = selend;
1221 }
1222
1223 viewInfo.selectedRegion.setTimes(sel0, sel1);
1224}
1225
1227 int mouseYCoordinate, int trackTopEdge,
1228 int trackHeight, ChannelView *pChannelView)
1229{
1230 mFreqSelTrack.reset();
1231 mFreqSelMode = FREQ_SEL_INVALID;
1233
1234 if (isSpectralSelectionView(pChannelView)) {
1235 // Spectral selection track is always wave
1236 auto shTrack = pChannelView->FindTrack()->SharedPointer<const WaveTrack>();
1237 mFreqSelTrack = shTrack;
1238 mFreqSelMode = FREQ_SEL_FREE;
1239 mFreqSelPin =
1240 PositionToFrequency(shTrack.get(), false, mouseYCoordinate,
1241 trackTopEdge, trackHeight);
1243 }
1244}
1245
1247 const WaveTrack *wt, ViewInfo &viewInfo,
1248 int mouseYCoordinate, int trackTopEdge,
1249 int trackHeight)
1250{
1251 if (mFreqSelMode == FREQ_SEL_INVALID ||
1252 mFreqSelMode == FREQ_SEL_SNAPPING_CENTER)
1253 return;
1254
1255 // Extension happens only when dragging in the same track in which we
1256 // started, and that is of a spectrogram display type.
1257
1258 const double rate = wt->GetRate();
1259 const double frequency =
1260 PositionToFrequency(wt, true, mouseYCoordinate,
1261 trackTopEdge, trackHeight);
1262
1263 // Dragging center?
1264 if (mFreqSelMode == FREQ_SEL_DRAG_CENTER) {
1265 if (frequency == rate || frequency < 1.0)
1266 // snapped to top or bottom
1270 else {
1271 // mFreqSelPin holds the ratio of top to center
1272 const double maxRatio = findMaxRatio(frequency, rate);
1273 const double ratio = std::min(maxRatio, mFreqSelPin);
1275 frequency / ratio, frequency * ratio);
1276 }
1277 }
1278 else if (mFreqSelMode == FREQ_SEL_PINNED_CENTER) {
1279 if (mFreqSelPin >= 0) {
1280 // Change both upper and lower edges leaving centre where it is.
1281 if (frequency == rate || frequency < 1.0)
1282 // snapped to top or bottom
1286 else {
1287 // Given center and mouse position, find ratio of the larger to the
1288 // smaller, limit that to the frequency scale bounds, and adjust
1289 // top and bottom accordingly.
1290 const double maxRatio = findMaxRatio(mFreqSelPin, rate);
1291 double ratio = frequency / mFreqSelPin;
1292 if (ratio < 1.0)
1293 ratio = 1.0 / ratio;
1294 ratio = std::min(maxRatio, ratio);
1296 mFreqSelPin / ratio, mFreqSelPin * ratio);
1297 }
1298 }
1299 }
1300 else {
1301 // Dragging of upper or lower.
1302 const bool bottomDefined =
1303 !(mFreqSelMode == FREQ_SEL_TOP_FREE && mFreqSelPin < 0);
1304 const bool topDefined =
1305 !(mFreqSelMode == FREQ_SEL_BOTTOM_FREE && mFreqSelPin < 0);
1306 if (!bottomDefined || (topDefined && mFreqSelPin < frequency)) {
1307 // Adjust top
1308 if (frequency == rate)
1309 // snapped high; upper frequency is undefined
1311 else
1312 viewInfo.selectedRegion.setF1(std::max(1.0, frequency));
1313
1315 }
1316 else {
1317 // Adjust bottom
1318 if (frequency < 1.0)
1319 // snapped low; lower frequency is undefined
1321 else
1322 viewInfo.selectedRegion.setF0(std::min(rate / 2.0, frequency));
1323
1325 }
1326 }
1327}
1328
1330(const ViewInfo &viewInfo, bool shiftDown, const WaveTrack *pTrack, double value)
1331{
1332 if (shiftDown) {
1333 // Disable time selection
1334 mSelStartValid = false;
1335 mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
1336 mFreqSelPin = value;
1337 mFreqSelMode = FREQ_SEL_DRAG_CENTER;
1338 }
1339 else {
1340#ifndef SPECTRAL_EDITING_ESC_KEY
1341 // Start center snapping
1342 // Turn center snapping on (the only way to do this)
1343 mFreqSelMode = FREQ_SEL_SNAPPING_CENTER;
1344 // Disable time selection
1345 mSelStartValid = false;
1346 mFrequencySnapper = std::make_shared<SpectrumAnalyst>();
1348#endif
1349 }
1350}
1351
1353 (SpectrumAnalyst &analyst,
1354 const ViewInfo &viewInfo, const WaveTrack *pTrack)
1355{
1356 static const size_t minLength = 8;
1357
1358 const double rate = pTrack->GetRate();
1359
1360 // Grab samples, just for this track, at these times
1361 std::vector<float> frequencySnappingData;
1362 const auto start =
1363 pTrack->TimeToLongSamples(viewInfo.selectedRegion.t0());
1364 const auto end =
1365 pTrack->TimeToLongSamples(viewInfo.selectedRegion.t1());
1366 const auto length =
1367 std::min(frequencySnappingData.max_size(),
1368 limitSampleBufferSize(10485760, // as in FreqWindow.cpp
1369 end - start));
1370 const auto effectiveLength = std::max(minLength, length);
1371 frequencySnappingData.resize(effectiveLength, 0.0f);
1372 pTrack->GetFloats(
1373 &frequencySnappingData[0],
1374 start, length, FillFormat::fillZero,
1375 // Don't try to cope with exceptions, just read zeroes instead.
1376 false);
1377
1378 // Use same settings as are now used for spectrogram display,
1379 // except, shrink the window as needed so we get some answers
1380
1381 const auto &settings = SpectrogramSettings::Get(*pTrack);
1382 auto windowSize = settings.GetFFTLength();
1383
1384 while(windowSize > effectiveLength)
1385 windowSize >>= 1;
1386 const int windowType = settings.windowType;
1387
1388 analyst.Calculate(
1389 SpectrumAnalyst::Spectrum, windowType, windowSize, rate,
1390 &frequencySnappingData[0], length);
1391
1392 // We can now throw away the sample data but we keep the spectrum.
1393}
1394
1396 AudacityProject *pProject, ViewInfo &viewInfo, int mouseYCoordinate,
1397 int trackTopEdge,
1398 int trackHeight, ChannelView *pChannelView)
1399{
1400 auto pTrack = pChannelView->FindTrack().get();
1401 if (pTrack &&
1402 pTrack->GetSelected() &&
1403 isSpectralSelectionView(pChannelView)) {
1404 // Spectral selection track is always wave
1405 WaveTrack *const wt = static_cast<WaveTrack*>(pTrack);
1406 // PRL:
1407 // What would happen if center snapping selection began in one spectrogram track,
1408 // then continues inside another? We do not then recalculate
1409 // the spectrum (as was done in StartSnappingFreqSelection)
1410 // but snap according to the peaks in the old track.
1411
1412 // But if we always supply the original clicked track here that doesn't matter.
1413 const double rate = wt->GetRate();
1414 const double frequency =
1415 PositionToFrequency(wt, false, mouseYCoordinate,
1416 trackTopEdge, trackHeight);
1417 const double snappedFrequency =
1418 mFrequencySnapper->FindPeak(frequency, NULL);
1419 const double maxRatio = findMaxRatio(snappedFrequency, rate);
1420 double ratio = 2.0; // An arbitrary octave on each side, at most
1421 {
1422 const double f0 = viewInfo.selectedRegion.f0();
1423 const double f1 = viewInfo.selectedRegion.f1();
1424 if (f1 >= f0 && f0 >= 0)
1425 // Preserve already chosen ratio instead
1426 ratio = sqrt(f1 / f0);
1427 }
1428 ratio = std::min(ratio, maxRatio);
1429
1430 mFreqSelPin = snappedFrequency;
1432 snappedFrequency / ratio, snappedFrequency * ratio);
1433
1434 // A change here would affect what AdjustFreqSelection() does
1435 // in the prototype version where you switch from moving center to
1436 // dragging width with a click. No effect now.
1437 mFreqSelTrack = wt->SharedPointer<const WaveTrack>();
1438
1439 // SelectNone();
1440 // SelectTrack(pTrack, true);
1441 TrackFocus::Get( *pProject ).Set(pTrack);
1442 }
1443}
1444
1446 (SpectrumAnalyst &analyst,
1447 ViewInfo &viewInfo, const WaveTrack *pTrack, bool up)
1448{
1449 const auto &settings = SpectrogramSettings::Get(*pTrack);
1450 const auto windowSize = settings.GetFFTLength();
1451 const double rate = pTrack->GetRate();
1452 const double nyq = rate / 2.0;
1453 const double binFrequency = rate / windowSize;
1454
1455 double f1 = viewInfo.selectedRegion.f1();
1456 double centerFrequency = viewInfo.selectedRegion.fc();
1457 if (centerFrequency <= 0) {
1458 centerFrequency = up ? binFrequency : nyq;
1459 f1 = centerFrequency * sqrt(2.0);
1460 }
1461
1462 double ratio = f1 / centerFrequency;
1463 const int originalBin = floor(0.5 + centerFrequency / binFrequency);
1464 const int limitingBin = up ? floor(0.5 + nyq / binFrequency) : 1;
1465
1466 // This is crude and wasteful, doing the FFT each time the command is called.
1467 // It would be better to cache the data, but then invalidation of the cache would
1468 // need doing in all places that change the time selection.
1469 StartSnappingFreqSelection(analyst, viewInfo, pTrack);
1470 double snappedFrequency = centerFrequency;
1471 int bin = originalBin;
1472 if (up) {
1473 while (snappedFrequency <= centerFrequency &&
1474 bin < limitingBin)
1475 snappedFrequency = analyst.FindPeak(++bin * binFrequency, NULL);
1476 }
1477 else {
1478 while (snappedFrequency >= centerFrequency &&
1479 bin > limitingBin)
1480 snappedFrequency = analyst.FindPeak(--bin * binFrequency, NULL);
1481 }
1482
1483 // PRL: added these two lines with the big TrackPanel refactor
1484 const double maxRatio = findMaxRatio(snappedFrequency, rate);
1485 ratio = std::min(ratio, maxRatio);
1486
1488 (snappedFrequency / ratio, snappedFrequency * ratio);
1489}
1490
1491#if 0
1492// unused
1493void SelectHandle::ResetFreqSelectionPin
1494 (const ViewInfo &viewInfo, double hintFrequency, bool logF)
1495{
1496 switch (mFreqSelMode) {
1497 case FREQ_SEL_INVALID:
1499 mFreqSelPin = -1.0;
1500 break;
1501
1503 mFreqSelPin = viewInfo.selectedRegion.fc();
1504 break;
1505
1507 {
1508 // Re-pin the width
1509 const double f0 = viewInfo.selectedRegion.f0();
1510 const double f1 = viewInfo.selectedRegion.f1();
1511 if (f0 >= 0 && f1 >= 0)
1512 mFreqSelPin = sqrt(f1 / f0);
1513 else
1514 mFreqSelPin = -1.0;
1515 }
1516 break;
1517
1518 case FREQ_SEL_FREE:
1519 // Pin which? Farther from the hint which is the presumed
1520 // mouse position.
1521 {
1522 // If this function finds use again, the following should be
1523 // generalized using NumberScale
1524
1525 const double f0 = viewInfo.selectedRegion.f0();
1526 const double f1 = viewInfo.selectedRegion.f1();
1527 if (logF) {
1528 if (f1 < 0)
1529 mFreqSelPin = f0;
1530 else {
1531 const double logf1 = log(std::max(1.0, f1));
1532 const double logf0 = log(std::max(1.0, f0));
1533 const double logHint = log(std::max(1.0, hintFrequency));
1534 if (std::abs(logHint - logf1) < std::abs(logHint - logf0))
1535 mFreqSelPin = f0;
1536 else
1537 mFreqSelPin = f1;
1538 }
1539 }
1540 else {
1541 if (f1 < 0 ||
1542 std::abs(hintFrequency - f1) < std::abs(hintFrequency - f0))
1543 mFreqSelPin = f0;
1544 else
1545 mFreqSelPin = f1;
1546 }
1547 }
1548 break;
1549
1550 case FREQ_SEL_TOP_FREE:
1551 mFreqSelPin = viewInfo.selectedRegion.f0();
1552 break;
1553
1555 mFreqSelPin = viewInfo.selectedRegion.f1();
1556 break;
1557
1558 default:
1559 wxASSERT(false);
1560 }
1561}
1562#endif
wxT("CloseDown"))
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
int min(int a, int b)
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
@ FREQ_SNAP_DISTANCE
@ SELECTION_RESIZE_REGION
std::vector< SnapPoint > SnapPointArray
Definition: Snap.h:43
const auto project
#define A(N)
Definition: ToChars.cpp:62
static Settings & settings()
Definition: TrackInfo.cpp:83
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:187
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:168
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
static ChannelView & Get(Channel &channel)
virtual bool IsSpectral() const
static CommandManager & Get(AudacityProject &project)
NormalizedKeyString GetKeyFromName(const CommandID &name) const
std::shared_ptr< Track > FindTrack()
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:87
double t1() const
Definition: ViewInfo.h:36
double f1() const
Definition: ViewInfo.h:38
bool setTimes(double t0, double t1)
Definition: ViewInfo.cpp:51
bool setF0(double f, bool maySwap=true)
Definition: ViewInfo.cpp:115
double fc() const
Definition: ViewInfo.h:39
bool setFrequencies(double f0, double f1)
Definition: ViewInfo.cpp:105
bool isPoint() const
Definition: ViewInfo.h:40
double f0() const
Definition: ViewInfo.h:37
double t0() const
Definition: ViewInfo.h:35
bool setF1(double f, bool maySwap=true)
Definition: ViewInfo.cpp:125
float PositionToValue(float pp) const
Definition: NumberScale.h:155
float ValueToPosition(float val) const
Definition: NumberScale.h:256
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
A move-only handle representing a connection to a Publisher.
Definition: Observer.h:70
bool IsAudioActive() const
static ProjectAudioIO & Get(AudacityProject &project)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
int GetTool() const
PlaybackScroller & GetPlaybackScroller()
static ProjectWindow & Get(AudacityProject &project)
void OnTimer(Observer::Message)
TimerHandler(SelectHandle *pParent, AudacityProject *pProject)
AudacityProject * mConnectedProject
Observer::Subscription mSubscription
SelectHandle(const SelectHandle &)
bool mAutoScrolling
Definition: SelectHandle.h:167
Result Cancel(AudacityProject *) override
wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass) override
virtual ~SelectHandle()
bool HasSnap() const
SnapResults mSnapEnd
Definition: SelectHandle.h:136
void HandleCenterFrequencyClick(const ViewInfo &viewInfo, bool shiftDown, const WaveTrack *pTrack, double value)
std::weak_ptr< Track > FindTrack()
std::weak_ptr< const WaveTrack > mFreqSelTrack
Definition: SelectHandle.h:155
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
void Enter(bool forward, AudacityProject *pProject) override
void SetUseSnap(bool use, AudacityProject *pProject)
SnapResults mSnapStart
Definition: SelectHandle.h:136
void AssignSelection(ViewInfo &viewInfo, double selend, Track *pTrack)
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
static void SnapCenterOnce(SpectrumAnalyst &analyst, ViewInfo &viewInfo, const WaveTrack *pTrack, bool up)
bool HasEscape(AudacityProject *pProject) const override
void StartFreqSelection(ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge, int trackHeight, ChannelView *pChannelView)
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
bool mSelStartValid
Definition: SelectHandle.h:139
std::shared_ptr< TimerHandler > mTimerHandler
Definition: SelectHandle.h:173
std::shared_ptr< SelectionStateChanger > mSelectionStateChanger
Definition: SelectHandle.h:169
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
std::weak_ptr< ChannelView > mpView
Definition: SelectHandle.h:131
static UIHandlePtr HitTest(std::weak_ptr< SelectHandle > &holder, const TrackPanelMouseState &state, const AudacityProject *pProject, const std::shared_ptr< ChannelView > &pChannelView)
std::shared_ptr< SpectrumAnalyst > mFrequencySnapper
Definition: SelectHandle.h:163
static void StartSnappingFreqSelection(SpectrumAnalyst &analyst, const ViewInfo &viewInfo, const WaveTrack *pTrack)
bool IsClicked() const
void AdjustSelection(AudacityProject *pProject, ViewInfo &viewInfo, int mouseXCoordinate, int trackLeftEdge, Track *pTrack)
Extend or contract the existing selection.
double mSelStart
Definition: SelectHandle.h:140
bool Escape(AudacityProject *pProject) override
std::shared_ptr< SnapManager > mSnapManager
Definition: SelectHandle.h:135
enum SelectHandle::eFreqSelMode FREQ_SEL_INVALID
@ FREQ_SEL_SNAPPING_CENTER
Definition: SelectHandle.h:147
@ FREQ_SEL_PINNED_CENTER
Definition: SelectHandle.h:148
static UIHandle::Result NeedChangeHighlight(const SelectHandle &oldState, const SelectHandle &newState)
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
int mSelectionBoundary
Definition: SelectHandle.h:142
void AdjustFreqSelection(const WaveTrack *wt, ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge, int trackHeight)
SelectedRegion mInitialSelection
Definition: SelectHandle.h:133
void MoveSnappingFreqSelection(AudacityProject *pProject, ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge, int trackHeight, ChannelView *pChannelView)
void Connect(AudacityProject *pProject)
double mFreqSelPin
Definition: SelectHandle.h:162
void StartSelection(AudacityProject *pProject)
Reset our selection markers.
static const int UndefinedFrequency
static void SelectTrackLength(ViewInfo &viewInfo, Track &track, bool syncLocked)
static SelectionState & Get(AudacityProject &project)
Definition: Snap.h:31
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.
Used for finding the peaks, for snapping to peaks.
float FindPeak(float xPos, float *pY) const
bool Calculate(Algorithm alg, int windowFunc, size_t windowSize, double rate, const float *data, size_t dataLen, float *pYMin=NULL, float *pYMax=NULL, FreqGauge *progress=NULL)
bool IsSyncLocked() const
Definition: SyncLock.cpp:43
static SyncLockState & Get(AudacityProject &project)
Definition: SyncLock.cpp:26
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:123
std::shared_ptr< Subclass > SharedPointer()
Definition: Track.h:161
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:987
std::shared_ptr< Subclass > Lock(const std::weak_ptr< Subclass > &wTrack)
Definition: Track.h:1240
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:354
static wxRect MaximizeHeight(const wxRect &rect, const wxRect &panelRect)
void Refresh(bool eraseBackground=true, const wxRect *rect=(const wxRect *) NULL) override
Definition: TrackPanel.cpp:784
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:232
Holds a msgid for the translation catalog; may also bind format arguments.
TranslatableString & Join(TranslatableString arg, const wxString &separator={}) &
Append another translatable string.
Result mChangeHighlight
Definition: UIHandle.h:139
unsigned Result
Definition: UIHandle.h:38
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:219
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:103
A Track that contains audio waveform data.
Definition: WaveTrack.h:220
const WaveClip * GetClipAtTime(double time) const
Definition: WaveTrack.cpp:3381
double GetRate() const override
Definition: WaveTrack.cpp:868
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=FillFormat::fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
"narrow" overload fetches first channel only
Definition: SampleTrack.h:46
sampleCount TimeToLongSamples(double t0) const
double PositionToTime(int64 position, int64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:35
int64 TimeToPosition(double time, int64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:45
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
void DoListSelection(AudacityProject &project, Track &t, bool shift, bool ctrl, bool modifyState)
double findMaxRatio(double center, double rate)
void SetTipAndCursorForBoundary(SelectionBoundary boundary, bool frequencySnapping, TranslatableString &tip, wxCursor *&pCursor)
wxInt64 FrequencyToPosition(const WaveTrack *wt, double frequency, wxInt64 trackTopEdge, int trackHeight)
Converts a frequency to screen y position.
double PositionToFrequency(const WaveTrack *wt, bool maySnap, wxInt64 mouseYCoordinate, wxInt64 trackTopEdge, int trackHeight)
void SetIfNotNull(T *pValue, const T Value)
SelectionBoundary ChooseTimeBoundary(const double t0, const double t1, const ViewInfo &viewInfo, double selend, bool onlyWithinSnapDistance, wxInt64 *pPixelDist, double *pPinValue)
bool isSpectralSelectionView(const ChannelView *pChannelView)
SelectionBoundary ChooseBoundary(const ViewInfo &viewInfo, wxCoord xx, wxCoord yy, const ChannelView *pChannelView, const wxRect &rect, bool mayDragWidth, bool onlyWithinSnapDistance, double *pPinValue=NULL)
__finl float_x4 __vecc sqrt(const float_x4 &a)
STL namespace.
wxString Display(bool usesSpecialChars=false) const
Definition: Keyboard.cpp:56
Default message type for Publisher.
Definition: Observer.h:26
bool snappedTime
Definition: Snap.h:50
double outTime
Definition: Snap.h:47
double timeSnappedTime
Definition: Snap.h:46
wxInt64 outCoord
Definition: Snap.h:48
bool Snapped() const
Definition: Snap.h:52
bool snappedPoint
Definition: Snap.h:49
std::shared_ptr< TrackPanelCell > pCell