1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-04 01:29:43 +02:00

Some tidying of scrub engine code, no audible changes

This commit is contained in:
Paul Licameli 2016-05-22 15:28:03 -04:00
commit 0bc47c9394
7 changed files with 161 additions and 149 deletions

View File

@ -344,6 +344,8 @@ double AudioIO::mCachedBestRateOut;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
#include "tracks/ui/Scrubbing.h"
/*
This work queue class, with the aid of the playback ring
buffers, coordinates three threads during scrub play:
@ -370,26 +372,25 @@ So a small, fixed queue size should be adequate.
struct AudioIO::ScrubQueue
{
ScrubQueue(double t0, double t1, wxLongLong startClockMillis,
double minTime, double maxTime,
double rate, double maxSpeed, double minStutter)
double rate, double maxSpeed,
const ScrubbingOptions &options)
: mTrailingIdx(0)
, mMiddleIdx(1)
, mLeadingIdx(2)
, mMinSample(minTime * rate)
, mMaxSample(maxTime * rate)
, mRate(rate)
, mMinStutter(lrint(std::max(0.0, minStutter) * mRate))
, mLastScrubTimeMillis(startClockMillis)
, mUpdating()
{
bool success = InitEntry(mEntries[mMiddleIdx],
t0, t1, maxSpeed, false, NULL, false);
// Ignore options.adjustStart, pass false.
bool success = InitEntry(mEntries[mMiddleIdx], nullptr,
t0, t1, maxSpeed, false, false, options);
if (!success)
{
// StartClock equals now? Really?
--mLastScrubTimeMillis;
success = InitEntry(mEntries[mMiddleIdx],
t0, t1, maxSpeed, false, NULL, false);
success = InitEntry(mEntries[mMiddleIdx], nullptr,
t0, t1, maxSpeed, false, false, options);
}
wxASSERT(success);
@ -419,7 +420,7 @@ struct AudioIO::ScrubQueue
mAvailable.Signal();
}
bool Producer(double end, double maxSpeed, bool bySpeed, bool maySkip)
bool Producer(double end, double maxSpeed, const ScrubbingOptions &options)
{
// Main thread indicates a scrubbing interval
@ -436,8 +437,8 @@ struct AudioIO::ScrubQueue
// Might reject the request because of zero duration,
// or a too-short "stutter"
const bool success =
(InitEntry(mEntries[mLeadingIdx], startTime, end, maxSpeed,
bySpeed, &previous, maySkip));
(InitEntry(mEntries[mLeadingIdx], &previous, startTime, end, maxSpeed,
options.enqueueBySpeed, options.adjustStart, options));
if (success) {
mLeadingIdx = next;
mAvailable.Signal();
@ -530,9 +531,9 @@ private:
, mPlayed(0)
{}
bool Init(long s0, long s1, long duration, Entry *previous,
double maxSpeed, long minStutter, long minSample, long maxSample,
bool adjustStart)
bool Init(Entry *previous, long s0, long s1, long duration,
double maxSpeed, bool adjustStart,
const ScrubbingOptions &options)
{
if (duration <= 0)
return false;
@ -566,7 +567,7 @@ private:
maxed = true;
}
if (speed < GetMinScrubSpeed())
if (speed < ScrubbingOptions::MinAllowedScrubSpeed())
// Mixers were set up to go only so slowly, not slower.
// This will put a request for some silence in the work queue.
speed = 0.0;
@ -583,7 +584,7 @@ private:
// (Assume s0 is in bounds, because it is the last scrub's s1 which was checked.)
if (s1 != s0)
{
const long newS1 = std::max(minSample, std::min(maxSample, s1));
const long newS1 = std::max(options.minSample, std::min(options.maxSample, s1));
if (s1 != newS1)
{
long newDuration = long(duration * double(newS1 - s0) / (s1 - s0));
@ -601,7 +602,7 @@ private:
{
// When playback follows a fast mouse movement by "stuttering"
// at maximum playback, don't make stutters too short to be useful.
if (duration < minStutter)
if (duration < options.minStutter)
return false;
// Limit diff because this is seeking.
const long diff = lrint(std::min(1.0, speed) * duration);
@ -623,7 +624,7 @@ private:
// Adjust s1 again, and duration, if s1 is out of bounds. (Assume s0 is in bounds.)
if (s1 != s0)
{
const long newS1 = std::max(minSample, std::min(maxSample, s1));
const long newS1 = std::max(options.minSample, std::min(options.maxSample, s1));
if (s1 != newS1)
{
long newDuration = long(duration * double(newS1 - s0) / (s1 - s0));
@ -667,8 +668,9 @@ private:
long mPlayed;
};
bool InitEntry(Entry &entry, double t0, double end, double maxSpeed,
bool bySpeed, Entry *previous, bool maySkip)
bool InitEntry(Entry &entry, Entry *previous, double t0, double end, double maxSpeed,
bool bySpeed, bool adjustStart,
const ScrubbingOptions &options)
{
const wxLongLong clockTime(::wxGetLocalTimeMillis());
const long duration =
@ -678,8 +680,7 @@ private:
? s0 + lrint(duration * end) // end is a speed
: lrint(end * mRate); // end is a time
const bool success =
entry.Init(s0, s1, duration, previous, maxSpeed, mMinStutter,
mMinSample, mMaxSample, maySkip);
entry.Init(previous, s0, s1, duration, maxSpeed, adjustStart, options);
if (success)
mLastScrubTimeMillis = clockTime;
return success;
@ -690,9 +691,7 @@ private:
unsigned mTrailingIdx;
unsigned mMiddleIdx;
unsigned mLeadingIdx;
const long mMinSample, mMaxSample;
const double mRate;
const long mMinStutter;
wxLongLong mLastScrubTimeMillis;
mutable wxMutex mUpdating;
mutable wxCondition mAvailable { mUpdating };
@ -1520,12 +1519,14 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
#ifdef EXPERIMENTAL_MIDI_OUT
const NoteTrackArray &midiPlaybackTracks,
#endif
double sampleRate, double t0, double t1,
double t0, double t1,
const AudioIOStartStreamOptions &options)
{
if( IsBusy() )
return 0;
const auto &sampleRate = options.rate;
// We just want to set mStreamToken to -1 - this way avoids
// an extremely rare but possible race condition, if two functions
// somehow called StartStream at the same time...
@ -1582,26 +1583,27 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
mCaptureBuffers = NULL;
mResample = NULL;
double playbackTime = 4.0;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
bool scrubbing = (options.pScrubbingOptions != nullptr);
// Scrubbing is not compatible with looping or recording or a time track!
const double scrubDelay = lrint(options.scrubDelay * sampleRate) / sampleRate;
bool scrubbing = (scrubDelay > 0);
double maxScrubSpeed = options.maxScrubSpeed;
double minScrubStutter = options.minScrubStutter;
if (scrubbing)
{
const auto &scrubOptions = *options.pScrubbingOptions;
if (mCaptureTracks->size() > 0 ||
mPlayMode == PLAY_LOOPED ||
mTimeTrack != NULL ||
options.maxScrubSpeed < GetMinScrubSpeed())
{
scrubOptions.maxSpeed < ScrubbingOptions::MinAllowedScrubSpeed()) {
wxASSERT(false);
scrubbing = false;
}
}
if (scrubbing)
{
mPlayMode = PLAY_SCRUB;
else {
playbackTime = lrint(scrubOptions.delay * sampleRate) / sampleRate;
mPlayMode = PLAY_SCRUB;
}
}
#endif
@ -1642,11 +1644,6 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
// mouse input, so make fillings more and shorter.
// What Audio thread produces for playback is then consumed by the PortAudio
// thread, in many smaller pieces.
double playbackTime = 4.0;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if (scrubbing)
playbackTime = scrubDelay;
#endif
mPlaybackSamplesToCopy = playbackTime * mRate;
// Capacity of the playback buffer.
@ -1739,9 +1736,13 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
const Mixer::WarpOptions &warpOptions =
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
scrubbing ? Mixer::WarpOptions(GetMinScrubSpeed(), GetMaxScrubSpeed()) :
scrubbing
? Mixer::WarpOptions
(ScrubbingOptions::MinAllowedScrubSpeed(),
ScrubbingOptions::MaxAllowedScrubSpeed())
:
#endif
Mixer::WarpOptions(mTimeTrack);
Mixer::WarpOptions(mTimeTrack);
for (unsigned int i = 0; i < mPlaybackTracks->size(); i++)
{
@ -1859,10 +1860,11 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
delete mScrubQueue;
if (scrubbing)
{
const auto &scrubOptions = *options.pScrubbingOptions;
mScrubQueue =
new ScrubQueue(mT0, mT1, options.scrubStartClockTimeMillis,
0.0, options.maxScrubTime,
sampleRate, maxScrubSpeed, minScrubStutter);
new ScrubQueue(mT0, mT1, scrubOptions.startClockTimeMillis,
sampleRate, scrubOptions.maxSpeed,
*options.pScrubbingOptions);
mScrubDuration = 0;
mSilentScrub = false;
}
@ -2459,18 +2461,11 @@ bool AudioIO::IsPaused()
}
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
bool AudioIO::EnqueueScrubByPosition(double endTime, double maxSpeed, bool maySkip)
bool AudioIO::EnqueueScrub
(double endTimeOrSpeed, double maxSpeed, const ScrubbingOptions &options)
{
if (mScrubQueue)
return mScrubQueue->Producer(endTime, maxSpeed, false, maySkip);
else
return false;
}
bool AudioIO::EnqueueScrubBySignedSpeed(double speed, double maxSpeed, bool maySkip)
{
if (mScrubQueue)
return mScrubQueue->Producer(speed, maxSpeed, true, maySkip);
return mScrubQueue->Producer(endTimeOrSpeed, maxSpeed, options);
else
return false;
}

View File

@ -85,52 +85,35 @@ DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_AUDIOIO_PLAYBACK, -1);
DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_AUDIOIO_CAPTURE, -1);
DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_AUDIOIO_MONITOR, -1);
struct ScrubbingOptions;
// To avoid growing the argument list of StartStream, add fields here
struct AudioIOStartStreamOptions
{
AudioIOStartStreamOptions()
explicit
AudioIOStartStreamOptions(double rate_)
: timeTrack(NULL)
, listener(NULL)
, rate(rate_)
, playLooped(false)
, cutPreviewGapStart(0.0)
, cutPreviewGapLen(0.0)
, pStartTime(NULL)
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
, scrubDelay(0.0)
, maxScrubSpeed(1.0)
, minScrubStutter(0.0)
, scrubStartClockTimeMillis(-1)
, maxScrubTime(0.0)
#endif
{}
TimeTrack *timeTrack;
AudioIOListener* listener;
double rate;
bool playLooped;
double cutPreviewGapStart;
double cutPreviewGapLen;
double * pStartTime;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
// Positive value indicates that scrubbing will happen
// Non-null value indicates that scrubbing will happen
// (do not specify a time track, looping, or recording, which
// are all incompatible with scrubbing):
double scrubDelay;
// We need a limiting value for the speed of the first scrub
// interval:
double maxScrubSpeed;
// When maximum speed scrubbing skips to follow the mouse,
// this is the minimum amount of playback at the maximum speed:
double minScrubStutter;
// Scrubbing needs the time of start of the mouse movement that began
// the scrub:
wxLongLong scrubStartClockTimeMillis;
// usually from TrackList::GetEndTime()
double maxScrubTime;
ScrubbingOptions *pScrubbingOptions {};
#endif
};
@ -163,9 +146,8 @@ class AUDACITY_DLL_API AudioIO final {
#ifdef EXPERIMENTAL_MIDI_OUT
const NoteTrackArray &midiTracks,
#endif
double sampleRate, double t0, double t1,
const AudioIOStartStreamOptions &options =
AudioIOStartStreamOptions());
double t0, double t1,
const AudioIOStartStreamOptions &options);
/** \brief Stop recording, playback or input monitoring.
*
@ -180,34 +162,18 @@ class AUDACITY_DLL_API AudioIO final {
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
bool IsScrubbing() { return IsBusy() && mScrubQueue != 0; }
static double GetMaxScrubSpeed() { return 32.0; } // Is five octaves enough for your amusement?
static double GetMinScrubSpeed() { return 0.01; }
/** \brief enqueue a NEW end time, using the last end as the new start,
/** \brief enqueue a NEW scrub play interval, using the last end as the new start,
* to be played over the same duration, as between this and the last
* enqueuing (or the starting of the stream). Except, we do not exceed maximum
* scrub speed, so may need to adjust either the start or the end.
* If maySkip is true, then when mouse movement exceeds maximum scrub speed,
* If options.adjustStart is true, then when mouse movement exceeds maximum scrub speed,
* adjust the beginning of the scrub interval rather than the end, so that
* the scrub skips or "stutters" to stay near the cursor.
* But if the "stutter" is too short for the minimum, then there is no effect
* on the work queue.
* Return true if some work was really enqueued.
*/
bool EnqueueScrubByPosition(double endTime, double maxSpeed, bool maySkip);
/** \brief enqueue a NEW positive or negative scrubbing speed,
* using the last end as the NEW start,
* to be played over the same duration, as between this and the last
* enqueueing (or the starting of the stream). Except, we do not exceed maximum
* scrub speed, so may need to adjust either the start or the end.
* If maySkip is true, then when mouse movement exceeds maximum scrub speed,
* adjust the beginning of the scrub interval rather than the end, so that
* the scrub skips or "stutters" to stay near the cursor.
* But if the "stutter" is too short for the minimum, then there is no effect
* on the work queue.
* Return true if some work was really enqueued.
*/
bool EnqueueScrubBySignedSpeed(double speed, double maxSpeed, bool maySkip);
bool EnqueueScrub(double endTimeOrSpeed, double maxSpeed, const ScrubbingOptions &options);
/** \brief return the ending time of the last enqueued scrub interval.
*/

View File

@ -1131,7 +1131,7 @@ AudacityProject::~AudacityProject()
AudioIOStartStreamOptions AudacityProject::GetDefaultPlayOptions()
{
AudioIOStartStreamOptions options;
AudioIOStartStreamOptions options { GetRate() };
options.timeTrack = GetTracks()->GetTimeTrack();
options.listener = this;
return options;

View File

@ -2534,7 +2534,7 @@ void Effect::Preview(bool dryOnly)
double previewLen;
gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLen, 6.0);
double rate = mProjectRate;
const double rate = mProjectRate;
if (isNyquist && isGenerator) {
previewDuration = CalcPreviewInputLength(previewLen);
@ -2637,12 +2637,13 @@ void Effect::Preview(bool dryOnly)
NoteTrackArray empty;
#endif
// Start audio playing
AudioIOStartStreamOptions options { rate };
int token =
gAudioIO->StartStream(playbackTracks, recordingTracks,
#ifdef EXPERIMENTAL_MIDI_OUT
empty,
#endif
rate, mT0, t1);
mT0, t1, options);
if (token) {
int previewing = eProgressSuccess;

View File

@ -634,7 +634,7 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
#ifdef EXPERIMENTAL_MIDI_OUT
NoteTrackArray(),
#endif
p->GetRate(), tcp0, tcp1, myOptions);
tcp0, tcp1, myOptions);
} else
{
// Cannot create cut preview tracks, clean up and exit
@ -655,7 +655,7 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
#ifdef EXPERIMENTAL_MIDI_OUT
t->GetNoteTrackArray(false),
#endif
p->GetRate(), t0, t1, options);
t0, t1, options);
}
if (token != 0) {
success = true;
@ -1085,7 +1085,7 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
#ifdef EXPERIMENTAL_MIDI_OUT
midiTracks,
#endif
p->GetRate(), t0, t1, options);
t0, t1, options);
bool success = (token != 0);

View File

@ -20,6 +20,12 @@ Paul Licameli split from TrackPanel.cpp
#include "../../TrackPanelCellIterator.h"
#include "../../commands/CommandFunctors.h"
#include "../../toolbars/ControlToolBar.h"
#undef USE_TRANSCRIPTION_TOOLBAR
#ifdef USE_TRANSCRIPTION_TOOLBAR
#include "../../toolbars/TranscriptionToolBar.h"
#endif
#include "../../widgets/Ruler.h"
#include <algorithm>
@ -42,6 +48,8 @@ enum {
ScrubPollInterval_ms = 50,
};
static const double MinStutter = 0.2;
namespace {
double FindScrubbingSpeed(const ViewInfo &viewInfo, double maxScrubSpeed, double screen, double timeAtMouse)
{
@ -136,11 +144,9 @@ void Scrubber::ScrubPoller::Notify()
Scrubber::Scrubber(AudacityProject *project)
: mScrubToken(-1)
, mScrubStartClockTimeMillis(-1)
, mScrubHasFocus(false)
, mPaused(true)
, mScrubSpeedDisplayCountdown(0)
, mScrubStartPosition(-1)
, mMaxScrubSpeed(-1.0)
, mScrubSeekPress(false)
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
, mSmoothScrollingScrub(false)
@ -149,6 +155,7 @@ Scrubber::Scrubber(AudacityProject *project)
, mProject(project)
, mPoller { std::make_unique<ScrubPoller>(*this) }
, mOptions {}
{
if (wxTheApp)
wxTheApp->Connect
@ -236,7 +243,7 @@ void Scrubber::MarkScrubStart(
ctb->UpdateStatusBar(mProject);
mScrubStartPosition = xx;
mScrubStartClockTimeMillis = ::wxGetLocalTimeMillis();
mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis();
CheckMenuItem();
}
@ -291,24 +298,28 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
}
AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions());
options.pScrubbingOptions = &mOptions;
options.timeTrack = NULL;
options.scrubDelay = (ScrubPollInterval_ms / 1000.0);
options.scrubStartClockTimeMillis = mScrubStartClockTimeMillis;
options.minScrubStutter = 0.2;
#if 0
mOptions.delay = (ScrubPollInterval_ms / 1000.0);
#ifdef USE_TRANSCRIPTION_TOOLBAR
if (!mAlwaysSeeking) {
// Take the starting speed limit from the transcription toolbar,
// but it may be varied during the scrub.
mMaxScrubSpeed = options.maxScrubSpeed =
p->GetTranscriptionToolBar()->GetPlaySpeed();
mOptions.maxSpeed =
mProject->GetTranscriptionToolBar()->GetPlaySpeed();
}
#else
// That idea seems unpopular... just make it one for move-scrub,
// but big for drag-scrub
mMaxScrubSpeed = options.maxScrubSpeed =
mDragging ? AudioIO::GetMaxScrubSpeed() : 1.0;
mOptions.maxSpeed =
mDragging ? ScrubbingOptions::MaxAllowedScrubSpeed() : 1.0;
#endif
options.maxScrubTime = mProject->GetTracks()->GetEndTime();
mOptions.minSample = 0;
mOptions.maxSample =
lrint(std::max(0.0, mProject->GetTracks()->GetEndTime()) * options.rate);
mOptions.minStutter =
lrint(std::max(0.0, MinStutter) * options.rate);
ControlToolBar::PlayAppearance appearance =
ControlToolBar::PlayAppearance::Scrub;
const bool cutPreview = false;
@ -317,7 +328,7 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
static const double maxScrubSpeedBase =
pow(2.0, 1.0 / ScrubSpeedStepsPerOctave);
mLogMaxScrubSpeed = floor(0.5 +
log(mMaxScrubSpeed) / log(maxScrubSpeedBase)
log(mOptions.maxSpeed) / log(maxScrubSpeedBase)
);
#endif
mScrubSpeedDisplayCountdown = 0;
@ -328,13 +339,13 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
}
else
// Wait to test again
mScrubStartClockTimeMillis = ::wxGetLocalTimeMillis();
mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis();
if (IsScrubbing()) {
using Mode = AudacityProject::PlaybackScroller::Mode;
mProject->GetPlaybackScroller().Activate
(mSmoothScrollingScrub ? Mode::Centered : Mode::Off);
mScrubHasFocus = true;
mPaused = false;
mLastScrubPosition = xx;
mPoller->Start(ScrubPollInterval_ms);
@ -357,7 +368,7 @@ void Scrubber::ContinueScrubbing()
// Thus scrubbing relies mostly on periodic polling of mouse and keys,
// not event notifications. But there are a few event handlers that
// leave messages for this routine, in mScrubSeekPress and in mScrubHasFocus.
// leave messages for this routine, in mScrubSeekPress and in mPaused.
// Seek only when the pointer is in the panel. Else, scrub.
TrackPanel *const trackPanel = mProject->GetTrackPanel();
@ -380,29 +391,37 @@ void Scrubber::ContinueScrubbing()
const auto &viewInfo = mProject->GetViewInfo();
bool result = false;
if (!mScrubHasFocus)
// When we don't have focus, enqueue silent scrubs until we regain focus.
result = gAudioIO->EnqueueScrubBySignedSpeed(0, mMaxScrubSpeed, false);
if (mPaused) {
// When paused, enqueue silent scrubs.
mOptions.adjustStart = false;
mOptions.enqueueBySpeed = true;
result = gAudioIO->EnqueueScrub(0, mOptions.maxSpeed, mOptions);
}
else if (mDragging && mSmoothScrollingScrub) {
const auto lastTime = gAudioIO->GetLastTimeInScrubQueue();
const auto delta = mLastScrubPosition - position.x;
const double time = viewInfo.OffsetTimeByPixels(lastTime, delta);
result = gAudioIO->EnqueueScrubByPosition(time, mMaxScrubSpeed, true);
mOptions.adjustStart = true;
mOptions.enqueueBySpeed = false;
result = gAudioIO->EnqueueScrub(time, mOptions.maxSpeed, mOptions);
mLastScrubPosition = position.x;
}
else {
const double time = viewInfo.PositionToTime(position.x, trackPanel->GetLeftOffset());
mOptions.adjustStart = seek;
if (seek)
// Cause OnTimer() to suppress the speed display
mScrubSpeedDisplayCountdown = 1;
if (mSmoothScrollingScrub) {
const double speed = FindScrubSpeed(seek, time);
result = gAudioIO->EnqueueScrubBySignedSpeed(speed, mMaxScrubSpeed, seek);
mOptions.enqueueBySpeed = true;
result = gAudioIO->EnqueueScrub(speed, mOptions.maxSpeed, mOptions);
}
else {
mOptions.enqueueBySpeed = false;
result = gAudioIO->EnqueueScrub(time, seek ? 1.0 : mOptions.maxSpeed, mOptions);
}
else
result = gAudioIO->EnqueueScrubByPosition
(time, seek ? 1.0 : mMaxScrubSpeed, seek);
}
if (result)
@ -460,11 +479,11 @@ bool Scrubber::ShouldDrawScrubSpeed()
return false;
return IsScrubbing() &&
mScrubHasFocus && (
// Draw for (non-scroll) scrub, sometimes, but never for seek
(!PollIsSeeking() && mScrubSpeedDisplayCountdown > 0)
// Draw always for scroll-scrub and for scroll-seek
|| mSmoothScrollingScrub
!mPaused && (
// Draw for (non-scroll) scrub, sometimes, but never for seek
(!PollIsSeeking() && mScrubSpeedDisplayCountdown > 0)
// Draw always for scroll-scrub and for scroll-seek
|| mSmoothScrollingScrub
);
}
@ -473,7 +492,7 @@ double Scrubber::FindScrubSpeed(bool seeking, double time) const
ViewInfo &viewInfo = mProject->GetViewInfo();
const double screen = mProject->GetScreenEndTime() - viewInfo.h;
return (seeking ? FindSeekSpeed : FindScrubbingSpeed)
(viewInfo, mMaxScrubSpeed, screen, time);
(viewInfo, mOptions.maxSpeed, screen, time);
}
void Scrubber::HandleScrollWheel(int steps)
@ -486,10 +505,10 @@ void Scrubber::HandleScrollWheel(int steps)
static const double maxScrubSpeedBase =
pow(2.0, 1.0 / ScrubSpeedStepsPerOctave);
double newSpeed = pow(maxScrubSpeedBase, newLogMaxScrubSpeed);
if (newSpeed >= AudioIO::GetMinScrubSpeed() &&
newSpeed <= AudioIO::GetMaxScrubSpeed()) {
if (newSpeed >= ScrubbingOptions::MinAllowedScrubSpeed() &&
newSpeed <= ScrubbingOptions::MaxAllowedScrubSpeed()) {
mLogMaxScrubSpeed = newLogMaxScrubSpeed;
mMaxScrubSpeed = newSpeed;
mOptions.maxSpeed = newSpeed;
if (!mSmoothScrollingScrub)
// Show the speed for one second
mScrubSpeedDisplayCountdown = kOneSecondCountdown + 1;
@ -498,12 +517,12 @@ void Scrubber::HandleScrollWheel(int steps)
void Scrubber::Pause( bool paused )
{
mScrubHasFocus = !paused;
mPaused = paused;
}
bool Scrubber::IsPaused() const
{
return !mScrubHasFocus;
return mPaused;
}
void Scrubber::OnActivateOrDeactivateApp(wxActivateEvent &event)

View File

@ -21,6 +21,38 @@ Paul Licameli split from TrackPanel.cpp
class AudacityProject;
// For putting an increment of work in the scrubbing queue
struct ScrubbingOptions {
ScrubbingOptions() {}
bool adjustStart {};
// usually from TrackList::GetEndTime()
long maxSample {};
long minSample {};
bool enqueueBySpeed {};
double delay {};
// A limiting value for the speed of a scrub interval:
double maxSpeed { 1.0 };
// When maximum speed scrubbing skips to follow the mouse,
// this is the minimum amount of playback allowed at the maximum speed:
long minStutter {};
// Scrubbing needs the time of start of the mouse movement that began
// the scrub:
wxLongLong startClockTimeMillis { -1 };
static double MaxAllowedScrubSpeed()
{ return 32.0; } // Is five octaves enough for your amusement?
static double MinAllowedScrubSpeed()
{ return 0.01; } // Mixer needs a lower bound speed. Scrub no slower than this.
};
// Scrub state object
class Scrubber : public wxEvtHandler
{
@ -61,7 +93,7 @@ public:
bool ShouldDrawScrubSpeed();
double FindScrubSpeed(bool seeking, double time) const;
double GetMaxScrubSpeed() const { return mMaxScrubSpeed; }
double GetMaxScrubSpeed() const { return mOptions.maxSpeed; }
void HandleScrollWheel(int steps);
@ -109,12 +141,10 @@ private:
private:
int mScrubToken;
wxLongLong mScrubStartClockTimeMillis;
bool mScrubHasFocus;
bool mPaused;
int mScrubSpeedDisplayCountdown;
wxCoord mScrubStartPosition;
wxCoord mLastScrubPosition {};
double mMaxScrubSpeed;
bool mScrubSeekPress;
bool mSmoothScrollingScrub;
bool mAlwaysSeeking {};
@ -130,6 +160,7 @@ private:
class ScrubPoller;
std::unique_ptr<ScrubPoller> mPoller;
ScrubbingOptions mOptions;
};
// Specialist in drawing the scrub speed, and listening for certain events