17#include "../../CommonCommandFlags.h"
20#include "../../ProjectAudioManager.h"
22#include "../../ProjectWindows.h"
24#include "../../ScrubState.h"
28#include "../../prefs/PlaybackPrefs.h"
29#include "../../prefs/TracksPrefs.h"
30#include "../../toolbars/ToolManager.h"
32#undef USE_TRANSCRIPTION_TOOLBAR
50#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
51 ScrubSpeedStepsPerOctave = 4,
71 const double origin = viewInfo.
h + partScreen;
72 if (timeAtMouse >= origin)
73 partScreen = screen - partScreen;
76 const double snap = 0.05;
80 const double factor = 1.0 - (snap * 2);
81 const double denom = factor * partScreen;
82 double fraction = (denom <= 0.0) ? 0.0 :
83 std::min(1.0, fabs(timeAtMouse - origin) / denom);
86 const double unity = 1.0 / maxScrubSpeed;
87 const double tolerance = snap / factor;
90 if (fraction <= unity - tolerance)
91 fraction *= unity / (unity - tolerance);
92 else if (fraction < unity + tolerance)
95 fraction = unity + (fraction - (unity + tolerance)) *
96 (1.0 - unity) / (1.0 - (unity + tolerance));
98 double result = fraction * maxScrubSpeed;
99 if (timeAtMouse < origin)
116 static const double ARBITRARY_MULTIPLIER = 10.0;
117 const double extreme = std::max(1.0, maxScrubSpeed * ARBITRARY_MULTIPLIER);
121 const double origin = viewInfo.
h + partScreen;
122 if (timeAtMouse >= origin)
123 partScreen = screen - partScreen;
127 const double snap = 0.05;
128 const double fraction = (partScreen <= 0.0) ? 0.0 :
129 std::max(snap,
std::min(1.0, fabs(timeAtMouse - origin) / partScreen));
131 double result = 1.0 + ((fraction - snap) / (1.0 - snap)) * (extreme - 1.0);
132 if (timeAtMouse < origin)
138#ifdef USE_SCRUB_THREAD
142 while (!mFinishThread.load(std::memory_order_acquire)) {
174#ifndef USE_SCRUB_THREAD
184 return std::make_shared< Scrubber >( &parent ); }
203#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
204 , mLogMaxScrubSpeed(0)
222#ifdef USE_SCRUB_THREAD
223 if (mThread.joinable()) {
224 mFinishThread.store(
true, std::memory_order_release);
241 return !range.empty();
269 {
wxT(
"Scrub"),
XXO(
"&Scrub"),
XO(
"Scrubbing"),
278 {
wxT(
"Seek"),
XXO(
"See&k"),
XO(
"Seeking"),
287 {
wxT(
"ToggleScrubRuler"),
XXO(
"Scrub &Ruler"), {},
299 return seek == item.
seek;
308 wxCoord xx,
bool smoothScrolling,
bool seek
321 projectAudioManager.Stop();
344 return [options](
auto&) -> std::unique_ptr<PlaybackPolicy>
346 return std::make_unique<ScrubbingPlaybackPolicy>(options);
351#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
359#ifdef USE_SCRUB_THREAD
360 if (mThread.joinable())
364 const auto state = ::wxGetMouseState();
368 const bool busy = gAudioIO->IsBusy();
369 if (busy && gAudioIO->GetNumCaptureChannels() > 0) {
376 wxCoord position = xx;
381 const int leftOffset = viewInfo.GetLeftOffset();
386 viewInfo.PositionToTime(position, leftOffset)
388 if (time1 != time0) {
391 projectAudioManager.Stop();
397 auto delta = time0 - time1;
398 time0 = std::max(0.0,
std::min(maxTime,
400 (viewInfo.GetScreenEndTime() - viewInfo.h)
403 time1 = time0 + delta;
411#ifndef USE_SCRUB_THREAD
415 options.playbackStreamPrimer = [
this](){
420 options.playNonWaveTracks =
false;
421 options.envelope =
nullptr;
426#ifdef USE_TRANSCRIPTION_TOOLBAR
427 if (!mAlwaysSeeking) {
452 const bool backwards = time1 < time0;
453#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
454 static const double maxScrubSpeedBase =
455 pow(2.0, 1.0 / ScrubSpeedStepsPerOctave);
456 mLogMaxScrubSpeed = floor(0.5 +
465 auto cleanup =
finally([
this]{
472 projectAudioManager.PlayPlayRegion(
502#ifdef USE_SCRUB_THREAD
503 if (mThread.joinable())
516#ifndef USE_SCRUB_THREAD
520 options.playbackStreamPrimer = [
this]() {
526 options.playNonWaveTracks =
false;
527 options.envelope =
nullptr;
548 auto cleanup =
finally([
this] {
567 const double speedAtDefaultZoom = 0.5;
568 const double maxSpeed = 3.0;
569 const double minSpeed = 0.0625;
572 double speed = speedAtDefaultZoom*viewInfo.GetDefaultZoom() / viewInfo.GetZoom();
574 speed = std::max(speed, minSpeed);
604 speed = projectAudioIO.GetPlaySpeed();
621 const wxMouseState state(::wxGetMouseState());
623 const wxPoint position = trackPanel.ScreenToClient(state.GetPosition());
629 const double time = viewInfo.OffsetTimeByPixels(lastTime, delta);
634 gAudioIO->UpdateScrub(time,
mOptions);
640 const auto origin = viewInfo.GetLeftOffset();
641 auto xx = position.x;
647 auto width = viewInfo.GetTracksUsableWidth();
648 auto delta = xx - origin;
651 else if (delta >= width)
655 const double time = viewInfo.PositionToTime(xx, origin);
680 const wxMouseState state(::wxGetMouseState());
685 bool bShift = state.ShiftDown();
687 projectAudioManager.DoPlayStopSelect(
true, bShift );
688 projectAudioManager.Stop();
726#ifdef USE_SCRUB_THREAD
727 assert(!mThread.joinable());
728 mFinishThread.store(
false, std::memory_order_relaxed);
729 mThread = std::thread{
741#ifdef USE_SCRUB_THREAD
755 const wxMouseState state(::wxGetMouseState());
757 bool bShift = state.ShiftDown();
759 projectAudioManager.DoPlayStopSelect(
true, bShift);
779 if (
mScrubToken == projectAudioIO.GetAudioIOToken() &&
780 projectAudioIO.IsAudioActive())
793#if !defined(DRAG_SCRUB)
832 const double screen =
833 viewInfo.GetScreenEndTime() - viewInfo.h;
843 const int newLogMaxScrubSpeed = mLogMaxScrubSpeed + steps;
844 static const double maxScrubSpeedBase =
845 pow(2.0, 1.0 / ScrubSpeedStepsPerOctave);
846 double newSpeed = pow(maxScrubSpeedBase, newLogMaxScrubSpeed);
849 mLogMaxScrubSpeed = newLogMaxScrubSpeed;
889 else if (!event.GetActive())
906 wxCoord xx = tp.ScreenToClient(::wxGetMouseState().GetPosition()).x;
909 auto width = viewInfo.GetTracksUsableWidth();
910 const auto offset = viewInfo.GetLeftOffset();
911 xx = (std::max(offset,
std::min(offset + width - 1, xx)));
920 projectAudioManager.Stop();
946 static const wxChar *scrubEnabledPrefName =
wxT(
"/QuickPlay/ScrubbingEnabled");
948 bool ReadScrubEnabledPref()
951 gPrefs->
Read(scrubEnabledPrefName, &result,
false);
956 void WriteScrubEnabledPref(
bool value)
979enum { CMD_ID = 8000 };
981#define THUNK(Name) Scrubber::Thunk<&Scrubber::Name>
983BEGIN_EVENT_TABLE(
Scrubber, wxEvtHandler)
986 EVT_MENU(CMD_ID + 2, THUNK(OnToggleScrubRuler))
991static auto sPlayAtSpeedStatus =
XO("Playing at Speed");
993static auto sKeyboardScrubbingStatus =
XO("Scrubbing");
1000 if (IsSpeedPlaying()) {
1001 return sPlayAtSpeedStatus;
1003 else if (IsKeyboardScrubbing()) {
1004 return sKeyboardScrubbingStatus;
1006 else if (HasMark()) {
1007 auto &item =
FindMenuItem(Seeks() || TemporarilySeeks());
1019 result =
_(
"Move mouse pointer to Seek");
1021 result =
_(
"Move mouse pointer to Scrub");
1036 strings.push_back( item.GetStatus() );
1038 XO(
"%s Paused.").Format( sPlayAtSpeedStatus )
1041 return { std::move( strings ), 30 };
1051 return !( gAudioIO->IsBusy() && gAudioIO->GetNumCaptureChannels() > 0 ) &&
1059 static double initT0 = 0;
1060 static double initT1 = 0;
1064 if (scrubber.IsKeyboardScrubbing() && scrubber.IsBackwards() == backwards) {
1066 auto time = gAudioIO->GetStreamTime();
1068 auto &selection = viewInfo.selectedRegion;
1072 if (selection.t0() == initT0 && selection.t1() == initT1) {
1075 time = std::max(time, 0.0);
1076 selection.setTimes(time, time);
1087 if (scrubber.IsKeyboardScrubbing() && scrubber.IsBackwards() != backwards) {
1089 scrubber.SetBackwards(backwards);
1091 else if (!gAudioIO->IsBusy() && !scrubber.HasMark()) {
1093 auto &selection = viewInfo.selectedRegion;
1095 double t0 = selection.t0();
1097 if ((!backwards && t0 >= 0 && t0 < endTime) ||
1098 (backwards && t0 > 0 && t0 <= endTime)) {
1100 initT1 = selection.t1();
1101 scrubber.StartKeyboardScrubbing(t0, backwards);
1109 auto evt = context.
pEvt;
1120 auto evt = context.
pEvt;
1131static const auto finder =
1139 static auto menu = []{
1141 auto menu = std::shared_ptr{
Menu(
"Scrubbing",
XXO(
"Scru&bbing")) };
1143 menu->push_back(
Command(item.name, item.label,
1149 return (Scrubber::Get(project).*(item.StatusTest))(); } )
1160 wxT(
"Transport/Basic"),
1171 Command(
wxT(
"KeyboardScrubBackwards"),
XXO(
"Scrub Bac&kwards"),
1175 Command(
wxT(
"KeyboardScrubForwards"),
XXO(
"Scrub For&wards"),
1184 wxT(
"Optional/Extra/Part1/Transport"),
1195 if (cm.GetEnabled(item.name)) {
1196 auto test = item.StatusTest;
1197 menu.Append(
id, item.label.Translation(), wxString{},
1198 test ? wxITEM_CHECK : wxITEM_NORMAL);
1199 if(test && (this->*test)())
1200 menu.FindItem(
id)->Check();
1210 auto test = item.StatusTest;
1212 cm.Check(item.name, (this->*test)());
EVT_MENU(OnSetPlayRegionToSelectionID, AdornedRulerPanel::OnSetPlayRegionToSelection) EVT_COMMAND(OnTogglePinnedStateID
static AudioUnitEffectsModule::Factory::SubstituteInUnique< AudioUnitEffect > scope
AttachedItem sAttachment2
constexpr CommandFlag AlwaysEnabledFlag
std::bitset< NCommandFlags > CommandFlag
wxEvtHandler CommandHandlerObject
const ReservedCommandFlag & CaptureNotBusyFlag()
XXO("&Cut/Copy/Paste Toolbar")
audacity::BasicSettings * gPrefs
AudioIOStartStreamOptions DefaultSpeedPlayOptions(AudacityProject &project)
const ReservedCommandFlag & CanStopAudioStreamFlag()
AUDACITY_DLL_API wxWindow & GetProjectPanel(AudacityProject &project)
Get the main sub-window of the project frame that displays track data.
static constexpr auto ScrubPollInterval
static const auto HasWaveDataPred
static const AudacityProject::AttachedObjects::RegisteredFactory key
@ SCRUBBING_PIXEL_TOLERANCE
static constexpr PlaybackPolicy::Duration MinStutter
static const ReservedCommandFlag & HasWaveDataFlag()
static AudioIOStartStreamOptions::PolicyFactory ScrubbingPlaybackPolicyFactory(const ScrubbingOptions &options)
declares abstract base class Track, TrackList, and iterators over TrackList
std::vector< TranslatableString > TranslatableStrings
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
static AudioIOBase * Get()
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
static CommandManager & Get(AudacityProject &project)
std::chrono::duration< double > Duration
static bool GetUnpinnedScrubbingPreference()
void SetAudioIOToken(int token)
static AudioIOStartStreamOptions GetDefaultOptions(AudacityProject &project, bool newDefaults=false)
Invoke the global hook, supplying a default argument.
static ProjectAudioIO & Get(AudacityProject &project)
void Stop(bool stopStream=true)
static ProjectAudioManager & Get(AudacityProject &project)
int PlayPlayRegion(const SelectedRegion &selectedRegion, const AudioIOStartStreamOptions &options, PlayMode playMode, bool backwards=false)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
std::pair< std::vector< TranslatableString >, unsigned > StatusWidthResult
ScrubPoller(Scrubber &scrubber)
void ContinueScrubbingUI()
void MarkScrubStart(wxCoord xx, bool smoothScrolling, bool seek)
bool ShouldDrawScrubSpeed()
void DoKeyboardScrub(bool backwards, bool keyUp)
double GetKeyboardScrubbingSpeed()
void HandleScrollWheel(int steps)
bool MaybeStartScrubbing(wxCoord xx)
wxCoord mLastScrubPosition
wxCoord mScrubStartPosition
bool mSmoothScrollingScrub
void OnToggleScrubRuler(const CommandContext &)
bool ChoseSeeking() const
bool TemporarilySeeks() const
std::unique_ptr< ScrubPoller > mPoller
static bool ShouldScrubPinned()
void OnActivateOrDeactivateApp(wxActivateEvent &event)
int mScrubSpeedDisplayCountdown
static Scrubber & Get(AudacityProject &project)
AudacityProject * mProject
void UpdatePrefs() override
ScrubbingOptions mOptions
void OnSeek(const CommandContext &)
bool StartKeyboardScrubbing(double time0, bool backwards)
void OnScrub(const CommandContext &)
void OnKeyboardScrubForwards(const CommandContext &)
bool MayDragToSeek() const
bool WasSpeedPlaying() const
void ContinueScrubbingPoll()
void OnKeyboardScrubBackwards(const CommandContext &)
bool IsTransportingPinned() const
void OnScrubOrSeek(bool seek)
void PopulatePopupMenu(wxMenu &menu)
wxString StatusMessageForWave() const
double FindScrubSpeed(bool seeking, double time) const
Scrubber(AudacityProject *project)
Defines a selected portion of a project.
double GetEndTime() const
Return the greatest end time of the tracks, or 0 when no tracks.
auto Any() -> TrackIterRange< TrackType >
static TrackList & Get(AudacityProject &project)
static bool GetPinnedHeadPreference()
static double GetPinnedHeadPositionPreference()
Holds a msgid for the translation catalog; may also bind format arguments.
static ViewInfo & Get(AudacityProject &project)
A Track that contains audio waveform data.
double GetStartTime() const override
Implement WideSampleSequence.
double GetEndTime() const override
Implement WideSampleSequence.
virtual bool Flush() noexcept=0
virtual bool Write(const wxString &key, bool value)=0
virtual bool Read(const wxString &key, bool *value) const =0
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
std::unique_ptr< detail::IndirectItem< Item > > Indirect(const std::shared_ptr< Item > &ptr)
A convenience function.
std::shared_ptr< BaseItem > BaseItemSharedPtr
CommandManager::Options Options
std::vector< MenuItem > MenuItems
const MenuItems & menuItems()
double FindSeekSpeed(const ViewInfo &viewInfo, double maxScrubSpeed, double screen, double timeAtMouse)
const MenuItem & FindMenuItem(bool seek)
double FindScrubbingSpeed(const ViewInfo &viewInfo, double maxScrubSpeed, double screen, double timeAtMouse)
std::function< std::unique_ptr< PlaybackPolicy >(const AudioIOStartStreamOptions &) > PolicyFactory
Options && CheckTest(const CheckFn &fn) &&
static double GetLastScrubTime()
return the ending time of the last scrub interval.
static void UpdateScrub(double endTimeOrSpeed, const ScrubbingOptions &options)
Notify scrubbing engine of desired position or speed. If options.adjustStart is true,...
PlaybackPolicy::Duration minStutterTime
PlaybackPolicy::Duration delay
static double MinAllowedScrubSpeed()
static double MaxAllowedScrubSpeed()