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:
commit
0bc47c9394
107
src/AudioIO.cpp
107
src/AudioIO.cpp
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -1131,7 +1131,7 @@ AudacityProject::~AudacityProject()
|
||||
|
||||
AudioIOStartStreamOptions AudacityProject::GetDefaultPlayOptions()
|
||||
{
|
||||
AudioIOStartStreamOptions options;
|
||||
AudioIOStartStreamOptions options { GetRate() };
|
||||
options.timeTrack = GetTracks()->GetTimeTrack();
|
||||
options.listener = this;
|
||||
return options;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user