mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-05 14:18:53 +02:00
Bug 133 - Enh: Play-at-Speed slider: Change of playback speed is no longer automatic
This adds a new preference to Playback prefs, VariSpeedPlay, on by default, which makes it possible to vary the playback speed slider whilst audio is playing using play at speed. The code uses the Scrubbing playback engine. This code is not final, but is already useful. Known issues: 1- Playback does not stop and pop-up the Play at Speed button at the end of playing the selection. 2- The scrub widget shows uselessly on the scrub ruler when in Play at Speed mode and the status bar proclaims 'Scrubbing'. 3- At low speeds the playback is clicky, with VariSpeedPlay, even if no changes to speed are being made. If we can't fix both 1 and 2 by 2.3.0 release time, VariSpeedPlay will be made false by default.
This commit is contained in:
parent
848b66f4fa
commit
17ca54ad4b
@ -115,6 +115,18 @@ void PlaybackPrefs::PopulateOrExchange(ShuttleGui & S)
|
||||
S.EndThreeColumn();
|
||||
}
|
||||
S.EndStatic();
|
||||
|
||||
S.StartStatic(_("Options"));
|
||||
{
|
||||
S.StartTwoColumn();
|
||||
{
|
||||
S.TieCheckBox("&Vari-Speed Play", "/AudioIO/VariSpeedPlay", true);
|
||||
}
|
||||
S.EndTwoColumn();
|
||||
}
|
||||
S.EndStatic();
|
||||
|
||||
|
||||
S.EndScroller();
|
||||
|
||||
}
|
||||
|
@ -40,6 +40,8 @@
|
||||
#include "../WaveTrack.h"
|
||||
#include "../widgets/AButton.h"
|
||||
#include "../widgets/ASlider.h"
|
||||
#include "../tracks/ui/Scrubbing.h"
|
||||
#include "../Prefs.h"
|
||||
|
||||
#ifdef EXPERIMENTAL_VOICE_DETECTION
|
||||
#include "../VoiceKey.h"
|
||||
@ -447,12 +449,22 @@ void TranscriptionToolBar::PlayAtSpeed(bool looped, bool cutPreview)
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a TimeTrack if we haven't done so already
|
||||
if (!mTimeTrack) {
|
||||
mTimeTrack = p->GetTrackFactory()->NewTimeTrack();
|
||||
// Fixed speed play is the old method, that uses a time track.
|
||||
// VariSpeed play reuses Scrubbing.
|
||||
bool bFixedSpeedPlay = !gPrefs->ReadBool(wxT("/AudioIO/VariSpeedPlay"), true);
|
||||
if (bFixedSpeedPlay)
|
||||
{
|
||||
// Create a TimeTrack if we haven't done so already
|
||||
if (!mTimeTrack) {
|
||||
return;
|
||||
mTimeTrack = p->GetTrackFactory()->NewTimeTrack();
|
||||
if (!mTimeTrack) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Set the speed range
|
||||
//mTimeTrack->SetRangeUpper((double)mPlaySpeed / 100.0);
|
||||
//mTimeTrack->SetRangeLower((double)mPlaySpeed / 100.0);
|
||||
mTimeTrack->GetEnvelope()->Flatten((double)mPlaySpeed / 100.0);
|
||||
}
|
||||
|
||||
// Pop up the button
|
||||
@ -463,18 +475,15 @@ void TranscriptionToolBar::PlayAtSpeed(bool looped, bool cutPreview)
|
||||
p->GetControlToolBar()->StopPlaying();
|
||||
}
|
||||
|
||||
// Set the speed range
|
||||
//mTimeTrack->SetRangeUpper((double)mPlaySpeed / 100.0);
|
||||
//mTimeTrack->SetRangeLower((double)mPlaySpeed / 100.0);
|
||||
mTimeTrack->GetEnvelope()->Flatten((double)mPlaySpeed / 100.0);
|
||||
|
||||
// Get the current play region
|
||||
double playRegionStart, playRegionEnd;
|
||||
p->GetPlayRegion(&playRegionStart, &playRegionEnd);
|
||||
|
||||
// Start playing
|
||||
if (playRegionStart >= 0) {
|
||||
// playRegionEnd = playRegionStart + (playRegionEnd-playRegionStart)* 100.0/mPlaySpeed;
|
||||
if (playRegionStart < 0)
|
||||
return;
|
||||
if (bFixedSpeedPlay)
|
||||
{
|
||||
AudioIOStartStreamOptions options(p->GetDefaultPlayOptions());
|
||||
options.playLooped = looped;
|
||||
options.timeTrack = mTimeTrack.get();
|
||||
@ -484,9 +493,14 @@ void TranscriptionToolBar::PlayAtSpeed(bool looped, bool cutPreview)
|
||||
: ControlToolBar::PlayAppearance::Straight;
|
||||
p->GetControlToolBar()->PlayPlayRegion
|
||||
(SelectedRegion(playRegionStart, playRegionEnd),
|
||||
options,
|
||||
PlayMode::normalPlay,
|
||||
appearance);
|
||||
options,
|
||||
PlayMode::normalPlay,
|
||||
appearance);
|
||||
}
|
||||
else
|
||||
{
|
||||
Scrubber &Scrubber = p->GetScrubber();
|
||||
Scrubber.StartSpeedPlay(GetPlaySpeed(), playRegionStart, playRegionEnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ Paul Licameli split from TrackPanel.cpp
|
||||
#include "../../toolbars/ControlToolBar.h"
|
||||
#include "../../toolbars/ScrubbingToolBar.h"
|
||||
#include "../../toolbars/ToolManager.h"
|
||||
#include "../../toolbars/TranscriptionToolBar.h"
|
||||
|
||||
#undef USE_TRANSCRIPTION_TOOLBAR
|
||||
#ifdef USE_TRANSCRIPTION_TOOLBAR
|
||||
@ -348,7 +349,7 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
|
||||
time1 = time0 + delta;
|
||||
}
|
||||
#endif
|
||||
|
||||
mSpeedPlaying = false;
|
||||
AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions());
|
||||
options.pScrubbingOptions = &mOptions;
|
||||
options.timeTrack = NULL;
|
||||
@ -432,6 +433,80 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Scrubber::StartSpeedPlay(double speed, double time0, double time1)
|
||||
{
|
||||
if (IsScrubbing())
|
||||
return false;
|
||||
|
||||
const bool busy = gAudioIO->IsBusy();
|
||||
if (busy && gAudioIO->GetNumCaptureChannels() > 0) {
|
||||
// Do not stop recording, and don't try to start scrubbing after
|
||||
// recording stops
|
||||
mScrubStartPosition = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
ControlToolBar * const ctb = mProject->GetControlToolBar();
|
||||
if (busy) {
|
||||
ctb->StopPlaying();
|
||||
}
|
||||
mScrubStartPosition = 0;
|
||||
mSpeedPlaying = true;
|
||||
mMaxSpeed = speed;
|
||||
mDragging = false;
|
||||
|
||||
AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions());
|
||||
options.pScrubbingOptions = &mOptions;
|
||||
options.timeTrack = NULL;
|
||||
mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis();
|
||||
mOptions.delay = (ScrubPollInterval_ms * 0.9 / 1000.0);
|
||||
mOptions.minSpeed = speed;
|
||||
mOptions.maxSpeed = speed;
|
||||
|
||||
if (time1 == time0)
|
||||
time1 = std::max(0.0, mProject->GetTracks()->GetEndTime());
|
||||
mOptions.minSample = 0;
|
||||
mOptions.maxSample = lrint(time1 * options.rate);
|
||||
mOptions.minStutter = lrint(std::max(0.0, MinStutter) * options.rate);
|
||||
mOptions.enqueueBySpeed = true;
|
||||
mOptions.adjustStart = false;
|
||||
|
||||
|
||||
ControlToolBar::PlayAppearance appearance = ControlToolBar::PlayAppearance::Straight;
|
||||
|
||||
const bool backwards = time1 < time0;
|
||||
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
|
||||
static const double maxScrubSpeedBase =
|
||||
pow(2.0, 1.0 / ScrubSpeedStepsPerOctave);
|
||||
mLogMaxScrubSpeed = floor(0.5 +
|
||||
log(mMaxSpeed) / log(maxScrubSpeedBase)
|
||||
);
|
||||
#endif
|
||||
|
||||
mScrubSpeedDisplayCountdown = 0;
|
||||
mScrubToken =
|
||||
ctb->PlayPlayRegion(SelectedRegion(time0, time1), options,
|
||||
PlayMode::normalPlay, appearance, backwards);
|
||||
|
||||
if (mScrubToken >= 0) {
|
||||
mPaused = false;
|
||||
mLastScrubPosition = 0;
|
||||
|
||||
#ifdef USE_SCRUB_THREAD
|
||||
// Detached thread is self-deleting, after it receives the Delete() message
|
||||
mpThread = safenew ScrubPollerThread{ *this };
|
||||
mpThread->Create(4096);
|
||||
mpThread->Run();
|
||||
#endif
|
||||
|
||||
mPoller->Start(ScrubPollInterval_ms);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Scrubber::ContinueScrubbingPoll()
|
||||
{
|
||||
// Thus scrubbing relies mostly on periodic polling of mouse and keys,
|
||||
@ -452,7 +527,20 @@ void Scrubber::ContinueScrubbingPoll()
|
||||
mOptions.enqueueBySpeed = true;
|
||||
result = gAudioIO->EnqueueScrub(0, mOptions);
|
||||
}
|
||||
else {
|
||||
else if (mSpeedPlaying) {
|
||||
// default speed of 1.3 set, so that we can hear there is a problem
|
||||
// when playAtSpeedTB not found.
|
||||
double speed = 1.3;
|
||||
TranscriptionToolBar *const playAtSpeedTB = mProject->GetTranscriptionToolBar();
|
||||
if (playAtSpeedTB) {
|
||||
speed = playAtSpeedTB->GetPlaySpeed();
|
||||
}
|
||||
mOptions.minSpeed = speed;
|
||||
mOptions.maxSpeed = speed;
|
||||
mOptions.adjustStart = false;
|
||||
mOptions.enqueueBySpeed = true;
|
||||
result = gAudioIO->EnqueueScrub(speed, mOptions);
|
||||
} else {
|
||||
const wxMouseState state(::wxGetMouseState());
|
||||
const auto trackPanel = mProject->GetTrackPanel();
|
||||
const wxPoint position = trackPanel->ScreenToClient(state.GetPosition());
|
||||
@ -687,10 +775,22 @@ bool Scrubber::IsPaused() const
|
||||
|
||||
void Scrubber::OnActivateOrDeactivateApp(wxActivateEvent &event)
|
||||
{
|
||||
if (event.GetActive())
|
||||
Pause(!IsScrubbing() || mProject->GetControlToolBar()->IsPauseDown());
|
||||
// First match priority logic...
|
||||
// Pause if Pause down, or not scrubbing.
|
||||
if (mProject->GetControlToolBar()->IsPauseDown())
|
||||
Pause( true );
|
||||
else if (!IsScrubbing())
|
||||
Pause( true );
|
||||
|
||||
// Speed playing does not pause if losing focus.
|
||||
else if (mSpeedPlaying)
|
||||
Pause( false );
|
||||
|
||||
// But scrub and seek do.
|
||||
else if (!event.GetActive())
|
||||
Pause( true );
|
||||
else
|
||||
Pause(true);
|
||||
Pause(false);
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ public:
|
||||
// Returns true iff the event should be considered consumed by this:
|
||||
// Assume xx is relative to the left edge of TrackPanel!
|
||||
bool MaybeStartScrubbing(wxCoord xx);
|
||||
bool StartSpeedPlay(double speed, double time0, double time1);
|
||||
|
||||
void ContinueScrubbingUI();
|
||||
void ContinueScrubbingPoll();
|
||||
@ -168,15 +169,15 @@ private:
|
||||
|
||||
private:
|
||||
int mScrubToken;
|
||||
bool mPaused;
|
||||
int mScrubSpeedDisplayCountdown;
|
||||
wxCoord mScrubStartPosition;
|
||||
wxCoord mLastScrubPosition {};
|
||||
bool mScrubSeekPress {};
|
||||
bool mSmoothScrollingScrub;
|
||||
|
||||
bool mPaused{};
|
||||
bool mSeeking {};
|
||||
|
||||
bool mSpeedPlaying{true};
|
||||
bool mDragging {};
|
||||
|
||||
bool mCancelled {};
|
||||
|
Loading…
x
Reference in New Issue
Block a user