Audacity  2.2.2
Scrubbing.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 Scrubbing.cpp
6 
7 Paul Licameli split from TrackPanel.cpp
8 
9 **********************************************************************/
10 
11 #include "../../Audacity.h"
12 #include "Scrubbing.h"
13 #include "../../Experimental.h"
14 #include <functional>
15 
16 #include "../../AudioIO.h"
17 #include "../../Project.h"
18 #include "../../TrackPanel.h"
19 #include "../../TrackPanelCell.h"
20 #include "../../commands/CommandFunctors.h"
21 #include "../../prefs/TracksPrefs.h"
22 #include "../../toolbars/ControlToolBar.h"
23 #include "../../toolbars/ScrubbingToolBar.h"
24 #include "../../toolbars/ToolManager.h"
25 
26 #undef USE_TRANSCRIPTION_TOOLBAR
27 #ifdef USE_TRANSCRIPTION_TOOLBAR
28 #include "../../toolbars/TranscriptionToolBar.h"
29 #endif
30 
31 #include "../../widgets/Ruler.h"
32 
33 #include <algorithm>
34 
35 #include <wx/app.h>
36 #include <wx/dc.h>
37 
38 // Yet another experimental scrub would drag the track under a
39 // stationary play head
40 #undef DRAG_SCRUB
41 
42 enum {
43  // PRL:
44  // Mouse must move at least this far to distinguish ctrl-drag to scrub
45  // from ctrl-click for playback.
47 
48 #ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
50 #endif
51 
53 
55 };
56 
57 static const double MinStutter = 0.2;
58 // static const double MaxDragSpeed = 1.0;
59 
60 namespace {
61  double FindScrubbingSpeed(const ViewInfo &viewInfo, double maxScrubSpeed, double screen, double timeAtMouse)
62  {
63  // Map a time (which was mapped from a mouse position)
64  // to a speed.
65  // Map times to positive and negative speeds,
66  // with the time at the midline of the screen mapping to 0,
67  // and the extremes to the maximum scrub speed.
68 
69  // Width of visible track area, in time terms:
70  const double origin = viewInfo.h + screen / 2.0;
71 
72  // There are various snapping zones that are this fraction of screen:
73  const double snap = 0.05;
74 
75  // By shrinking denom a bit, we make margins left and right
76  // that snap to maximum and negative maximum speeds.
77  const double factor = 1.0 - (snap * 2);
78  const double denom = factor * screen / 2.0;
79  double fraction = std::min(1.0, fabs(timeAtMouse - origin) / denom);
80 
81  // Snap to 1.0 and -1.0
82  const double unity = 1.0 / maxScrubSpeed;
83  const double tolerance = snap / factor;
84  // Make speeds near 1 available too by remapping fractions outside
85  // this snap zone
86  if (fraction <= unity - tolerance)
87  fraction *= unity / (unity - tolerance);
88  else if (fraction < unity + tolerance)
89  fraction = unity;
90  else
91  fraction = unity + (fraction - (unity + tolerance)) *
92  (1.0 - unity) / (1.0 - (unity + tolerance));
93 
94  double result = fraction * maxScrubSpeed;
95  if (timeAtMouse < origin)
96  result *= -1.0;
97  return result;
98  }
99 
100  double FindSeekSpeed(const ViewInfo &viewInfo, double maxScrubSpeed, double screen, double timeAtMouse)
101  {
102  // Map a time (which was mapped from a mouse position)
103  // to a signed skip speed: a multiplier of the stutter duration,
104  // by which to advance the play position.
105  // (The stutter will play at unit speed.)
106 
107  // Times near the midline of the screen map to skip-less play,
108  // and the extremes to a value proportional to maximum scrub speed.
109 
110  // If the maximum scrubbing speed defaults to 1.0 when you begin to scroll-scrub,
111  // the extreme skipping for scroll-seek needs to be larger to be useful.
112  static const double ARBITRARY_MULTIPLIER = 10.0;
113  const double extreme = std::max(1.0, maxScrubSpeed * ARBITRARY_MULTIPLIER);
114 
115  // Width of visible track area, in time terms:
116  const double halfScreen = screen / 2.0;
117  const double origin = viewInfo.h + halfScreen;
118 
119  // The snapping zone is this fraction of screen, on each side of the
120  // center line:
121  const double snap = 0.05;
122  const double fraction =
123  std::max(snap, std::min(1.0, fabs(timeAtMouse - origin) / halfScreen));
124 
125  double result = 1.0 + ((fraction - snap) / (1.0 - snap)) * (extreme - 1.0);
126  if (timeAtMouse < origin)
127  result *= -1.0;
128  return result;
129  }
130 }
131 
132 #ifdef USE_SCRUB_THREAD
133 
134 class Scrubber::ScrubPollerThread final : public wxThread {
135 public:
137  : wxThread { }
138  , mScrubber(scrubber)
139  {}
140  ExitCode Entry() override;
141 
142 private:
144 };
145 
147 {
148  while( !TestDestroy() )
149  {
150  wxThread::Sleep(ScrubPollInterval_ms);
151  mScrubber.ContinueScrubbingPoll();
152  }
153  return 0;
154 }
155 
156 #endif
157 
158 class Scrubber::ScrubPoller : public wxTimer
159 {
160 public:
161  ScrubPoller(Scrubber &scrubber) : mScrubber( scrubber ) {}
162 
163 private:
164  void Notify() override;
165 
167 };
168 
170 {
171  // Call Continue functions here in a timer handler
172  // rather than in SelectionHandleDrag()
173  // so that even without drag events, we can instruct the play head to
174  // keep approaching the mouse cursor, when its maximum speed is limited.
175 
176 #ifndef USE_SCRUB_THREAD
177  // If there is no helper thread, this main thread timer is responsible
178  // for playback and for UI
180 #endif
182 }
183 
185  : mScrubToken(-1)
186  , mPaused(true)
188  , mScrubStartPosition(-1)
190  , mSmoothScrollingScrub(false)
191  , mLogMaxScrubSpeed(0)
192 #endif
193 
194  , mProject(project)
195  , mPoller { std::make_unique<ScrubPoller>(*this) }
196  , mOptions {}
197  , mInOneShotMode( false )
198 
199 {
200  if (wxTheApp)
201  wxTheApp->Bind
202  (wxEVT_ACTIVATE_APP,
204  mProject->PushEventHandler(&mForwarder);
205 }
206 
208 {
209 #ifdef USE_SCRUB_THREAD
210  if (mpThread)
211  mpThread->Delete();
212 #endif
213 
214  mProject->PopEventHandler();
215 }
216 
217 namespace {
218  const struct MenuItem {
219  wxString name;
220  wxString label;
221  wxString status;
222  CommandFlag flags;
223  void (Scrubber::*memFn)(const CommandContext&);
224  bool seek;
225  bool (Scrubber::*StatusTest)() const;
226 
227  const wxString &GetStatus() const { return status; }
228  } menuItems[] = {
229  /* i18n-hint: These commands assist the user in finding a sound by ear. ...
230  "Scrubbing" is variable-speed playback, ...
231  "Seeking" is normal speed playback but with skips, ...
232  */
233  { wxT("Scrub"), XO("&Scrub"), XO("Scrubbing"),
236  },
237 
238  { wxT("Seek"), XO("See&k"), XO("Seeking"),
241  },
242 
243  { wxT("ToggleScrubRuler"), XO("Scrub &Ruler"), wxT(""),
246  },
247  };
248 
249  enum { nMenuItems = sizeof(menuItems) / sizeof(*menuItems) };
250 
251  inline const MenuItem &FindMenuItem(bool seek)
252  {
253  return *std::find_if(menuItems, menuItems + nMenuItems,
254  [=](const MenuItem &item) {
255  return seek == item.seek;
256  }
257  );
258  }
259 
260 }
261 
263  // Assume xx is relative to the left edge of TrackPanel!
264  wxCoord xx, bool smoothScrolling, bool seek
265 )
266 {
267  // Don't actually start scrubbing, but collect some information
268  // needed for the decision to start scrubbing later when handling
269  // drag events.
270  mSmoothScrollingScrub = smoothScrolling;
271 
272  ControlToolBar * const ctb = mProject->GetControlToolBar();
273 
274  // Stop any play in progress
275  // Bug 1492: mCancelled to stop us collapsing the selected region.
276  mCancelled = true;
277  ctb->StopPlaying();
278  mCancelled = false;
279 
280  // Usually the timer handler of TrackPanel does this, but we do this now,
281  // so that same timer does not StopPlaying() again after this function and destroy
282  // scrubber state
284 
285  mSeeking = seek;
286  CheckMenuItems();
287 
289  // Commented out for Bug 1421
290  // mSeeking
291  // ? ControlToolBar::PlayAppearance::Seek
292  // : ControlToolBar::PlayAppearance::Scrub);
293 
294  mScrubStartPosition = xx;
296  mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis();
297  mCancelled = false;
298 }
299 
300 #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
301 // Assume xx is relative to the left edge of TrackPanel!
303 {
304  if (mScrubStartPosition < 0)
305  return false;
306  if (IsScrubbing())
307  return false;
308  else {
309  const auto state = ::wxGetMouseState();
310  mDragging = state.LeftIsDown();
311 
312  const bool busy = gAudioIO->IsBusy();
313  if (busy && gAudioIO->GetNumCaptureChannels() > 0) {
314  // Do not stop recording, and don't try to start scrubbing after
315  // recording stops
316  mScrubStartPosition = -1;
317  return false;
318  }
319 
320  wxCoord position = xx;
321  if (abs(mScrubStartPosition - position) >= SCRUBBING_PIXEL_TOLERANCE) {
322  const ViewInfo &viewInfo = mProject->GetViewInfo();
323  TrackPanel *const trackPanel = mProject->GetTrackPanel();
324  ControlToolBar * const ctb = mProject->GetControlToolBar();
325  double maxTime = mProject->GetTracks()->GetEndTime();
326  const int leftOffset = trackPanel->GetLeftOffset();
327  double time0 = std::min(maxTime,
328  viewInfo.PositionToTime(mScrubStartPosition, leftOffset)
329  );
330  double time1 = std::min(maxTime,
331  viewInfo.PositionToTime(position, leftOffset)
332  );
333  if (time1 != time0)
334  {
335  if (busy) {
336  auto position = mScrubStartPosition;
337  ctb->StopPlaying();
338  mScrubStartPosition = position;
339  }
340 
341 #ifdef DRAG_SCRUB
343  auto delta = time0 - time1;
344  time0 = std::max(0.0, std::min(maxTime,
345  (viewInfo.h + mProject->GetScreenEndTime()) / 2
346  ));
347  time1 = time0 + delta;
348  }
349 #endif
350 
352  options.pScrubbingOptions = &mOptions;
353  options.timeTrack = NULL;
354  mOptions.delay = (ScrubPollInterval_ms * 0.9 / 1000.0);
355  mOptions.minSpeed = 0.0;
356 #ifdef USE_TRANSCRIPTION_TOOLBAR
357  if (!mAlwaysSeeking) {
358  // Take the starting speed limit from the transcription toolbar,
359  // but it may be varied during the scrub.
362  }
363 #else
364  // That idea seems unpopular... just make it one for move-scrub,
365  // but big for drag-scrub
366 #ifdef DRAG_SCRUB
367  mMaxSpeed = mOptions.maxSpeed = mDragging ? MaxDragSpeed : 1.0;
368 #else
369  mMaxSpeed = mOptions.maxSpeed = 1.0;
370 #endif
371 
372 #endif
373  mOptions.minSample = 0;
375  lrint(std::max(0.0, mProject->GetTracks()->GetEndTime()) * options.rate);
377 #ifdef DRAG_SCRUB
378  mDragging ? 0.0 :
379 #endif
380  lrint(std::max(0.0, MinStutter) * options.rate);
381 
382  ControlToolBar::PlayAppearance appearance =
383  // commented out to fix Bug 1241
384  // mSeeking
385  // ? ControlToolBar::PlayAppearance::Seek
386  // : ControlToolBar::PlayAppearance::Scrub;
388 // const bool cutPreview = false;
389  const bool backwards = time1 < time0;
390 #ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
391  static const double maxScrubSpeedBase =
392  pow(2.0, 1.0 / ScrubSpeedStepsPerOctave);
393  mLogMaxScrubSpeed = floor(0.5 +
394  log(mMaxSpeed) / log(maxScrubSpeedBase)
395  );
396 #endif
398  mScrubToken =
399  ctb->PlayPlayRegion(SelectedRegion(time0, time1), options,
400  PlayMode::normalPlay, appearance, backwards);
401  if (mScrubToken <= 0) {
402  // Bug1627 (part of it):
403  // infinite error spew when trying to start scrub:
404  // If failed for reasons of audio device problems, do not try
405  // again with repeated timer ticks.
406  mScrubStartPosition = -1;
407  return false;
408  }
409  }
410  }
411  else
412  // Wait to test again
413  mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis();
414 
415  if (IsScrubbing()) {
416  mPaused = false;
417  mLastScrubPosition = xx;
418 
419 #ifdef USE_SCRUB_THREAD
420  // Detached thread is self-deleting, after it receives the Delete() message
422  mpThread->Create(4096);
423  mpThread->Run();
424 #endif
425 
427  }
428 
429  // Return true whether we started scrub, or are still waiting to decide.
430  return true;
431  }
432 }
433 
435 {
436  // Thus scrubbing relies mostly on periodic polling of mouse and keys,
437  // not event notifications. But there are a few event handlers that
438  // leave messages for this routine, in mScrubSeekPress and in mPaused.
439 
440  // Decide whether to skip play, because either mouse is down now,
441  // or there was a left click event. (This is then a delayed reaction, in a
442  // timer callback, to a left click event detected elsewhere.)
443  const bool seek = TemporarilySeeks() || Seeks();
444 
445  bool result = false;
446  if (mPaused) {
447  // When paused, enqueue silent scrubs.
448  mOptions.minSpeed = 0.0;
450  mOptions.adjustStart = false;
451  mOptions.enqueueBySpeed = true;
452  result = gAudioIO->EnqueueScrub(0, mOptions);
453  }
454  else {
455  const wxMouseState state(::wxGetMouseState());
456  const auto trackPanel = mProject->GetTrackPanel();
457  const wxPoint position = trackPanel->ScreenToClient(state.GetPosition());
458  const auto &viewInfo = mProject->GetViewInfo();
459 #ifdef DRAG_SCRUB
461  const auto lastTime = gAudioIO->GetLastTimeInScrubQueue();
462  const auto delta = mLastScrubPosition - position.x;
463  const double time = viewInfo.OffsetTimeByPixels(lastTime, delta);
464  mOptions.minSpeed = 0.0;
466  mOptions.adjustStart = true;
467  mOptions.enqueueBySpeed = false;
468  result = gAudioIO->EnqueueScrub(time, mOptions);
469  mLastScrubPosition = position.x;
470  }
471  else
472 #endif
473  {
474  const double time = viewInfo.PositionToTime(position.x, trackPanel->GetLeftOffset());
475  mOptions.adjustStart = seek;
476  mOptions.minSpeed = seek ? 1.0 : 0.0;
477  mOptions.maxSpeed = seek ? 1.0 : mMaxSpeed;
478 
479  if (mSmoothScrollingScrub) {
480  const double speed = FindScrubSpeed(seek, time);
481  mOptions.enqueueBySpeed = true;
482  result = gAudioIO->EnqueueScrub(speed, mOptions);
483  }
484  else {
485  mOptions.enqueueBySpeed = false;
486  result = gAudioIO->EnqueueScrub(time, mOptions);
487  }
488  }
489  }
490 
491  if (result)
492  mScrubSeekPress = false;
493 
494  // else, if seek requested, try again at a later time when we might
495  // enqueue a long enough stutter
496 }
497 
499 {
500  const wxMouseState state(::wxGetMouseState());
501 
502  if (mDragging && !state.LeftIsDown()) {
503  // Dragging scrub can stop with mouse up
504  // Stop and set cursor
505  bool bShift = state.ShiftDown();
506  mProject->DoPlayStopSelect(true, bShift);
507  wxCommandEvent evt;
509  return;
510  }
511 
512  const bool seek = Seeks() || TemporarilySeeks();
513 
514  {
515  // Show the correct status for seeking.
516  bool backup = mSeeking;
517  mSeeking = seek;
518  const auto ctb = mProject->GetControlToolBar();
519  if (ctb)
521  mSeeking = backup;
522  }
523 
524  if (seek)
526 
528  ;
529  else {
532  }
533 }
534 
536 {
537 #ifdef USE_SCRUB_THREAD
538  if (mpThread) {
539  mpThread->Delete();
540  mpThread = nullptr;
541  }
542 #endif
543 
544  mPoller->Stop();
545 
546  if (HasStartedScrubbing() && !mCancelled) {
547  const wxMouseState state(::wxGetMouseState());
548  // Stop and set cursor
549  bool bShift = state.ShiftDown();
550  mProject->DoPlayStopSelect(true, bShift);
551  }
552 
553  mScrubStartPosition = -1;
554  mDragging = false;
555  mSeeking = false;
556 
557  if (!IsScrubbing())
558  {
559  // Marked scrub start, but
560  // didn't really play, but did change button apperance
561  const auto ctb = mProject->GetControlToolBar();
563  }
564 
566  CheckMenuItems();
567 }
568 
569 bool Scrubber::ShowsBar() const
570 {
572 }
573 
575 {
576  if (mScrubToken <= 0)
577  return false;
578  else if (mScrubToken == mProject->GetAudioIOToken() &&
580  return true;
581  else {
582  const_cast<Scrubber&>(*this).mScrubToken = -1;
583  const_cast<Scrubber&>(*this).mScrubStartPosition = -1;
584  const_cast<Scrubber&>(*this).mSmoothScrollingScrub = false;
585  return false;
586  }
587 }
588 
590 {
591  return
592 #if !defined(DRAG_SCRUB)
593  // Drag always seeks
594  mDragging ||
595 #endif
596  mSeeking;
597 }
598 
600 {
601  // Return true only if the pointer is in the
602  // ruler or the track panel
603  const auto &state = ::wxGetMouseState();
604  const auto &position = state.GetPosition();
605 
606  auto ruler = mProject->GetRulerPanel();
607  if (ruler &&
608  ruler->GetScreenRect().Contains(position))
609  return true;
610 
611  /*
612  auto trackPanel = mProject->GetTrackPanel();
613  if (trackPanel &&
614  trackPanel->GetScreenRect().Contains(position))
615  return true;
616  */
617 
618  return false;
619 }
620 
622 {
623  return mScrubSeekPress ||
624  (::wxGetMouseState().LeftIsDown() && MayDragToSeek());
625 }
626 
627 bool Scrubber::Seeks() const
628 {
629  return (HasStartedScrubbing() || IsScrubbing()) && ChoseSeeking();
630 }
631 
632 bool Scrubber::Scrubs() const
633 {
634  if( Seeks() )
635  return false;
636  return (HasStartedScrubbing() || IsScrubbing()) && !ChoseSeeking();
637 }
638 
640 {
641  return IsScrubbing() &&
642  !mPaused && (
643  // Draw for (non-scroll) scrub, sometimes, but never for seek
645  // Draw always for scroll-scrub and for scroll-seek
647  );
648 }
649 
650 double Scrubber::FindScrubSpeed(bool seeking, double time) const
651 {
652  ViewInfo &viewInfo = mProject->GetViewInfo();
653  const double screen = mProject->GetScreenEndTime() - viewInfo.h;
654  return (seeking ? FindSeekSpeed : FindScrubbingSpeed)
655  (viewInfo, mMaxSpeed, screen, time);
656 }
657 
659 {
660  if (steps == 0)
661  return;
662 
663  const int newLogMaxScrubSpeed = mLogMaxScrubSpeed + steps;
664  static const double maxScrubSpeedBase =
665  pow(2.0, 1.0 / ScrubSpeedStepsPerOctave);
666  double newSpeed = pow(maxScrubSpeedBase, newLogMaxScrubSpeed);
667  if (newSpeed >= ScrubbingOptions::MinAllowedScrubSpeed() &&
669  mLogMaxScrubSpeed = newLogMaxScrubSpeed;
670  mMaxSpeed = newSpeed;
672  // Show the speed for one second
674  }
675 }
676 
677 void Scrubber::Pause( bool paused )
678 {
679  mPaused = paused;
680 }
681 
682 bool Scrubber::IsPaused() const
683 {
684  return mPaused;
685 }
686 
687 void Scrubber::OnActivateOrDeactivateApp(wxActivateEvent &event)
688 {
689  if (event.GetActive())
691  else
692  Pause(true);
693 
694  event.Skip();
695 }
696 
697 void Scrubber::Forwarder::OnMouse(wxMouseEvent &event)
698 {
699  //auto ruler = scrubber.mProject->GetRulerPanel();
700  auto isScrubbing = scrubber.IsScrubbing();
701  if (isScrubbing && !event.HasAnyModifiers()) {
702  if(event.LeftDown() && scrubber.MayDragToSeek()) {
703  // This event handler may catch mouse transitions that are missed
704  // by the polling of mouse state by the timer.
705  scrubber.mScrubSeekPress = true;
706  }
707  else if (event.m_wheelRotation) {
708  double steps = event.m_wheelRotation /
709  (event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0);
711  }
712  else
713  event.Skip();
714  }
715  else
716  event.Skip();
717 }
718 
720 // class ScrubbingOverlay is responsible for drawing the speed numbers
721 
723  : mProject(project)
724  , mLastScrubRect()
725  , mNextScrubRect()
726  , mLastScrubSpeedText()
727  , mNextScrubSpeedText()
728 {
729  mProject->Bind(EVT_TRACK_PANEL_TIMER,
731  this);
732 }
733 
734 std::pair<wxRect, bool> ScrubbingOverlay::DoGetRectangle(wxSize)
735 {
736  wxRect rect(mLastScrubRect);
737  const bool outdated =
739  (!mLastScrubRect.IsEmpty() && !GetScrubber().ShouldDrawScrubSpeed()) ||
741  return std::make_pair(
742  rect,
743  outdated
744  );
745 }
746 
748 {
751 
752  Scrubber &scrubber = GetScrubber();
753  if (!scrubber.ShouldDrawScrubSpeed())
754  return;
755 
756  static const wxFont labelFont(24, wxSWISS, wxNORMAL, wxNORMAL);
757  dc.SetFont(labelFont);
758 
759  // These two colors were previously saturated red and green. However
760  // we have a rule to try to only use red for reserved purposes of
761  // (a) Recording
762  // (b) Error alerts
763  // So they were changed to 'orange' and 'lime'.
764  static const wxColour clrNoScroll(215, 162, 0), clrScroll(0, 204, 153);
765  if (scrubber.IsScrollScrubbing())
766  dc.SetTextForeground(clrScroll);
767  else
768  dc.SetTextForeground(clrNoScroll);
769 
770  dc.DrawText(mLastScrubSpeedText, mLastScrubRect.GetX(), mLastScrubRect.GetY());
771 }
772 
773 void ScrubbingOverlay::OnTimer(wxCommandEvent &event)
774 {
775  // Let other listeners get the notification
776  event.Skip();
777 
778  Scrubber &scrubber = GetScrubber();
779  const auto isScrubbing = scrubber.IsScrubbing();
780  const auto ruler = mProject->GetRulerPanel();
781  auto position = ::wxGetMousePosition();
782 
783  {
784  if(scrubber.HasStartedScrubbing()) {
785  auto xx = ruler->ScreenToClient(position).x;
786  ruler->UpdateQuickPlayPos(xx);
787 
788  if (!isScrubbing)
789  // Really start scrub if motion is far enough
790  scrubber.MaybeStartScrubbing(xx);
791  }
792 
793  if (!isScrubbing) {
794  mNextScrubRect = wxRect();
795  return;
796  }
797  else
798  ruler->ShowQuickPlayIndicator();
799  }
800 
801  if (!scrubber.ShouldDrawScrubSpeed()) {
802  mNextScrubRect = wxRect();
803  }
804  else {
805  TrackPanel *const trackPanel = mProject->GetTrackPanel();
806  int panelWidth, panelHeight;
807  trackPanel->GetSize(&panelWidth, &panelHeight);
808 
809  // Where's the mouse?
810  position = trackPanel->ScreenToClient(position);
811 
812  const bool seeking = scrubber.Seeks() || scrubber.TemporarilySeeks();
813 
814  // Find the text
815  const double maxScrubSpeed = GetScrubber().GetMaxScrubSpeed();
816  const double speed =
817  scrubber.IsScrollScrubbing()
818  ? scrubber.FindScrubSpeed
819  (seeking, mProject->GetViewInfo().PositionToTime(position.x, trackPanel->GetLeftOffset()))
820  : maxScrubSpeed;
821 
822  const wxChar *format =
823  scrubber.IsScrollScrubbing()
824  ? seeking
825  ? wxT("%+.2fX")
826  : wxT("%+.2f")
827  : wxT("%.2f");
828 
829  mNextScrubSpeedText = wxString::Format(format, speed);
830 
831  // Find the origin for drawing text
832  wxCoord width, height;
833  {
834  wxClientDC dc(trackPanel);
835  static const wxFont labelFont(24, wxSWISS, wxNORMAL, wxNORMAL);
836  dc.SetFont(labelFont);
837  dc.GetTextExtent(mNextScrubSpeedText, &width, &height);
838  }
839  const auto xx =
840  std::max(0, std::min(panelWidth - width, position.x - width / 2));
841 
842  // Put the text above the cursor, if it fits.
843  enum { offset = 20 };
844  auto yy = position.y - height + offset;
845  if (yy < 0)
846  yy += height + 2 * offset;
847  yy = std::max(0, std::min(panelHeight - height, yy));
848 
849  mNextScrubRect = wxRect(xx, yy, width, height);
850  }
851 }
852 
854 {
855  return mProject->GetScrubber();
856 }
857 
859 {
860  return mProject->GetScrubber();
861 }
862 
863 void Scrubber::DoScrub(bool seek)
864 {
865  if( !CanScrub() )
866  return;
867  const bool wasScrubbing = HasStartedScrubbing() || IsScrubbing();
868  const bool scroll = TracksPrefs::GetPinnedHeadPreference();
869  if (!wasScrubbing) {
870  auto tp = mProject->GetTrackPanel();
871  wxCoord xx = tp->ScreenToClient(::wxGetMouseState().GetPosition()).x;
872 
873  // Limit x
874  int width;
875  tp->GetTracksUsableArea(&width, nullptr);
876  const auto offset = tp->GetLeftOffset();
877  xx = (std::max(offset, std::min(offset + width - 1, xx)));
878 
879  MarkScrubStart(xx, scroll, seek);
880  }
881  else if (mSeeking != seek) {
882  // just switching mode
883  }
884  else
886 }
887 
888 void Scrubber::OnScrubOrSeek(bool seek)
889 {
890  DoScrub(seek);
891 
892  if (HasStartedScrubbing()) {
893  // Show the correct status.
894  const auto ctb = mProject->GetControlToolBar();
896  }
897 
898  mSeeking = seek;
899  CheckMenuItems();
900 
901  auto ruler = mProject->GetRulerPanel();
902  if (ruler)
903  // Update button images
904  ruler->UpdateButtonStates();
905 
906  auto scrubbingToolBar = mProject->GetScrubbingToolBar();
907  scrubbingToolBar->EnableDisableButtons();
908  scrubbingToolBar->RegenerateTooltips();
909 }
910 
912 {
913  OnScrubOrSeek(false);
914  CheckMenuItems();
915 }
916 
918 {
919  OnScrubOrSeek(true);
920  CheckMenuItems();
921 }
922 
924 {
926  const auto toolbar = mProject->GetToolManager()->GetToolBar(ScrubbingBarID);
927  toolbar->EnableDisableButtons();
928  CheckMenuItems();
929 }
930 
931 enum { CMD_ID = 8000 };
932 
933 #define THUNK(Name) Scrubber::Thunk<&Scrubber::Name>
934 
935 BEGIN_EVENT_TABLE(Scrubber, wxEvtHandler)
936  EVT_MENU(CMD_ID, THUNK(OnScrub))
937  EVT_MENU(CMD_ID + 1, THUNK(OnSeek))
938  EVT_MENU(CMD_ID + 2, THUNK(OnToggleScrubRuler))
940 
941 BEGIN_EVENT_TABLE(Scrubber::Forwarder, wxEvtHandler)
942  EVT_MOUSE_EVENTS(Scrubber::Forwarder::OnMouse)
944 
945 static_assert(nMenuItems == 3, "wrong number of items");
946 
947 const wxString &Scrubber::GetUntranslatedStateString() const
948 {
949  static wxString empty;
950 
951  if (HasStartedScrubbing()) {
952  auto &item = FindMenuItem(Seeks() || TemporarilySeeks());
953  return item.status;
954  }
955  else
956  return empty;
957 }
958 
960 {
961  wxString result;
962  result = "";
963 
964  if( Seeks() )
965  result = _("Move mouse pointer to Seek");
966  else if( Scrubs() )
967  result = _("Move mouse pointer to Scrub");
968  return result;
969 }
970 
971 
972 
974 {
975  using namespace std;
976  vector<wxString> results;
977  for (const auto &item : menuItems) {
978  const auto &status = item.GetStatus();
979  if (!status.empty())
980  results.push_back(status);
981  }
982  return move(results);
983 }
984 
985 bool Scrubber::CanScrub() const
986 {
987  // Return the enabled state for the menu item that really launches the scrub or seek.
988  auto cm = mProject->GetCommandManager();
989  return cm->GetEnabled(menuItems[ 0 ].name);
990 }
991 
992 // To supply the "finder" argument
994 { return project.GetScrubber(); }
995 
997 {
998  auto cm = mProject->GetCommandManager();
999 
1000  cm->BeginSubMenu(_("Scru&bbing"));
1001  for (const auto &item : menuItems) {
1002  if (item.StatusTest)
1003  cm->AddCheck(item.name, wxGetTranslation(item.label),
1004  findme, static_cast<CommandFunctorPointer>(item.memFn),
1005  false,
1006  item.flags, item.flags);
1007  else
1008  // The start item
1009  cm->AddItem(item.name, wxGetTranslation(item.label),
1010  findme, static_cast<CommandFunctorPointer>(item.memFn),
1011  item.flags, item.flags);
1012  }
1013  cm->EndSubMenu();
1014  CheckMenuItems();
1015 }
1016 
1018 {
1019  int id = CMD_ID;
1020  auto cm = mProject->GetCommandManager();
1021  for (const auto &item : menuItems) {
1022  if (cm->GetEnabled(item.name)) {
1023  auto test = item.StatusTest;
1024  menu.Append(id, wxGetTranslation(item.label), wxString{},
1025  test ? wxITEM_CHECK : wxITEM_NORMAL);
1026  if(test && (this->*test)())
1027  menu.FindItem(id)->Check();
1028  }
1029  ++id;
1030  }
1031 }
1032 
1034 {
1035  auto cm = mProject->GetCommandManager();
1036  for (const auto &item : menuItems) {
1037  auto test = item.StatusTest;
1038  if (test)
1039  cm->Check(item.name, (this->*test)());
1040  }
1041 }
1042 
1043 #endif
Scrubber & scrubber
Definition: Scrubbing.h:161
ScrubPollerThread(Scrubber &scrubber)
Definition: Scrubbing.cpp:136
const Scrubber & GetScrubber() const
Definition: Scrubbing.cpp:853
bool GetEnabled(const wxString &name)
void SetPlay(bool down, PlayAppearance appearance=PlayAppearance::Straight)
A ToolBar that has the main Transport buttons.
bool ChoseSeeking() const
Definition: Scrubbing.cpp:589
bool ShowingScrubRuler() const
Definition: Ruler.h:363
bool HasStartedScrubbing() const
Definition: Scrubbing.h:94
AudioIOStartStreamOptions GetDefaultPlayOptions()
Definition: Project.cpp:1267
sampleCount maxSample
Definition: Scrubbing.h:43
void Pause(bool paused)
Definition: Scrubbing.cpp:677
wxRect mNextScrubRect
Definition: Scrubbing.h:224
ViewInfo is used mainly to hold the zooming, selection and scroll information. It also has some statu...
Definition: ViewInfo.h:141
AudacityProject * mProject
Definition: Scrubbing.h:187
bool MayDragToSeek() const
Definition: Scrubbing.cpp:599
wxMenu * BeginSubMenu(const wxString &tName)
bool mScrubSeekPress
Definition: Scrubbing.h:174
Scrubber & GetScrubber()
Definition: Project.h:785
wxString mNextScrubSpeedText
Definition: Scrubbing.h:225
wxString mLastScrubSpeedText
Definition: Scrubbing.h:225
void OnScrub(const CommandContext &)
Definition: Scrubbing.cpp:911
void OnToggleScrubRuler()
Definition: Ruler.cpp:2874
bool mSeeking
Definition: Scrubbing.h:177
bool ShowsBar() const
Definition: Scrubbing.cpp:569
bool IsPaused() const
Definition: Scrubbing.cpp:682
double GetEndTime() const
Definition: Track.cpp:1409
void SetAudioIOToken(int token)
Definition: Project.cpp:1418
unsigned GetNumCaptureChannels() const
Definition: AudioIO.h:377
AudacityProject * mProject
Definition: Scrubbing.h:222
wxString label
Definition: Tags.cpp:727
static const double MinStutter
Definition: Scrubbing.cpp:57
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ViewInfo.cpp:49
bool mCancelled
Definition: Scrubbing.h:181
void OnSeek(const CommandContext &)
Definition: Scrubbing.cpp:917
double GetLastTimeInScrubQueue() const
return the ending time of the last enqueued scrub interval.
Definition: AudioIO.cpp:2881
void StopPlaying(bool stopStream=true)
int GetAudioIOToken() const
Definition: Project.cpp:1413
#define XO(s)
Definition: Internat.h:30
void CheckMenuItems()
Definition: Scrubbing.cpp:1033
CommandManager * GetCommandManager()
Definition: Project.h:328
bool IsBusy()
Returns true if audio i/o is busy starting, stopping, playing, or recording.
Definition: AudioIO.cpp:2891
ScrubbingOptions * pScrubbingOptions
Definition: AudioIO.h:139
double h
Definition: ViewInfo.h:47
ScrubPollerThread * mpThread
Definition: Scrubbing.h:195
bool mPaused
Definition: Scrubbing.h:170
bool IsPauseDown() const
#define safenew
Definition: Audacity.h:223
void HideQuickPlayIndicator(bool repaint_all=false)
Definition: Ruler.cpp:3327
ScrubbingOverlay(AudacityProject *project)
Definition: Scrubbing.cpp:722
void UpdateStatusBar(AudacityProject *pProject)
void GetTracksUsableArea(int *width, int *height) const
Definition: TrackPanel.cpp:421
int mScrubToken
Definition: Scrubbing.h:169
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:158
void StopScrubbing()
Definition: Scrubbing.cpp:535
std::pair< wxRect, bool > DoGetRectangle(wxSize size) override
Definition: Scrubbing.cpp:734
void AddMenuItems()
Definition: Scrubbing.cpp:996
static std::vector< wxString > GetAllUntranslatedStatusStrings()
Definition: Scrubbing.cpp:973
int mLogMaxScrubSpeed
Definition: Scrubbing.h:184
static double MaxAllowedScrubSpeed()
Definition: Scrubbing.h:63
double GetPlaySpeed() const
wxCoord mLastScrubPosition
Definition: Scrubbing.h:173
int format
Definition: ExportPCM.cpp:56
bool IsScrubbing() const
Definition: Scrubbing.cpp:574
#define EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
Definition: Project.h:212
void Check(const wxString &name, bool checked)
void ContinueScrubbingUI()
Definition: Scrubbing.cpp:498
Defines a selected portion of a project.
void ContinueScrubbingPoll()
Definition: Scrubbing.cpp:434
bool TemporarilySeeks() const
Definition: Scrubbing.cpp:621
The TrackPanel class coordinates updates and operations on the main part of the screen which contains...
Definition: TrackPanel.h:245
ScrubbingToolBar * GetScrubbingToolBar()
Definition: Project.cpp:4840
#define THUNK(Name)
Definition: Scrubbing.cpp:933
void Draw(OverlayPanel &panel, wxDC &dc) override
Definition: Scrubbing.cpp:747
bool MaybeStartScrubbing(wxCoord xx)
Definition: Scrubbing.cpp:302
void OnMouse(wxMouseEvent &event)
Definition: Scrubbing.cpp:697
void OnToggleScrubRuler(const CommandContext &)
Definition: Scrubbing.cpp:923
struct holding stream options, including a pointer to the TimeTrack and AudioIOListener and whether t...
Definition: AudioIO.h:114
AdornedRulerPanel * GetRulerPanel()
Definition: Project.cpp:1408
virtual void EnableDisableButtons()=0
wxString StatusMessageForWave() const
Definition: Scrubbing.cpp:959
#define lrint(dbl)
Definition: float_cast.h:136
double FindScrubSpeed(bool seeking, double time) const
Definition: Scrubbing.cpp:650
ToolManager * GetToolManager()
Definition: Project.h:688
bool EnqueueScrub(double endTimeOrSpeed, const ScrubbingOptions &options)
enqueue a NEW scrub play interval, using the last end as the NEW start, to be played over the same du...
Definition: AudioIO.cpp:2873
bool Seeks() const
Definition: Scrubbing.cpp:627
std::unique_ptr< ScrubPoller > mPoller
Definition: Scrubbing.h:200
void OnStop(wxCommandEvent &evt)
bool ShouldDrawScrubSpeed()
Definition: Scrubbing.cpp:639
int min(int a, int b)
double mMaxSpeed
Definition: Scrubbing.h:204
wxCoord mScrubStartPosition
Definition: Scrubbing.h:172
bool IsAudioActive() const
Definition: Project.cpp:1423
TranscriptionToolBar * GetTranscriptionToolBar()
Definition: Project.cpp:4882
bool CanScrub() const
Definition: Scrubbing.cpp:985
ScrubPoller(Scrubber &scrubber)
Definition: Scrubbing.cpp:161
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
double maxSpeed
Definition: Scrubbing.h:52
bool Scrubs() const
Definition: Scrubbing.cpp:632
CommandFlag
Definition: CommandFlag.h:16
bool mDragging
Definition: Scrubbing.h:179
AudioIO * gAudioIO
Definition: AudioIO.cpp:481
int PlayPlayRegion(const SelectedRegion &selectedRegion, const AudioIOStartStreamOptions &options, PlayMode playMode, PlayAppearance appearance=PlayAppearance::Straight, bool backwards=false, bool playWhiteSpace=false)
wxLongLong startClockTimeMillis
Definition: Scrubbing.h:61
ControlToolBar * GetControlToolBar()
Definition: Project.cpp:4803
const wxChar * name
Definition: Distortion.cpp:94
double minSpeed
Definition: Scrubbing.h:51
static bool GetPinnedHeadPreference()
Scrubber(AudacityProject *project)
Definition: Scrubbing.cpp:184
void OnActivateOrDeactivateApp(wxActivateEvent &event)
Definition: Scrubbing.cpp:687
bool IsScrollScrubbing() const
Definition: Scrubbing.h:98
void OnTimer(wxCommandEvent &event)
Definition: Scrubbing.cpp:773
double GetScreenEndTime() const
Definition: Menus.cpp:6443
sampleCount minSample
Definition: Scrubbing.h:44
void HandleScrollWheel(int steps)
Definition: Scrubbing.cpp:658
void PopulatePopupMenu(wxMenu &menu)
Definition: Scrubbing.cpp:1017
bool mSmoothScrollingScrub
Definition: Scrubbing.h:175
TrackPanel * GetTrackPanel()
Definition: Project.h:289
bool enqueueBySpeed
Definition: Scrubbing.h:46
void EnableDisableButtons() override
int GetLeftOffset() const
Definition: TrackPanel.h:287
void DoScrub(bool seek)
Definition: Scrubbing.cpp:863
END_EVENT_TABLE()
TrackList * GetTracks()
Definition: Project.h:174
ExitCode Entry() override
Definition: Scrubbing.cpp:146
double GetMaxScrubSpeed() const
Definition: Scrubbing.h:115
static CommandHandlerObject & findme(AudacityProject &project)
Definition: Scrubbing.cpp:993
const ViewInfo & GetViewInfo() const
Definition: Project.h:189
wxRect mLastScrubRect
Definition: Scrubbing.h:224
void MarkScrubStart(wxCoord xx, bool smoothScrolling, bool seek)
Definition: Scrubbing.cpp:262
void UpdateButtonStates()
Definition: Ruler.cpp:2895
wxEvtHandler CommandHandlerObject
ToolBar * GetToolBar(int type) const
ScrubbingOptions mOptions
Definition: Scrubbing.h:203
int mScrubSpeedDisplayCountdown
Definition: Scrubbing.h:171
void OnScrubOrSeek(bool seek)
Definition: Scrubbing.cpp:888
static double MinAllowedScrubSpeed()
Definition: Scrubbing.h:65
void Notify() override
Definition: Scrubbing.cpp:169
bool DoPlayStopSelect(bool click, bool shift)
Definition: Menus.cpp:2759