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