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