diff --git a/libraries/lib-audio-devices/AudioIOBase.h b/libraries/lib-audio-devices/AudioIOBase.h index 2ab870cf64babd3e99dd7158ac99bd3f2e07eb42..291d0539be5c0a32ac1c10acdc9f1ff1de01f0c6 100644 --- a/libraries/lib-audio-devices/AudioIOBase.h +++ b/libraries/lib-audio-devices/AudioIOBase.h @@ -70,10 +70,12 @@ struct AudioIOStartStreamOptions // The return value is a number of milliseconds to sleep before calling again std::function< unsigned long() > playbackStreamPrimer; - using PolicyFactory = std::function< std::unique_ptr<PlaybackPolicy>() >; + using PolicyFactory = std::function< + std::unique_ptr<PlaybackPolicy>(const AudioIOStartStreamOptions&) >; PolicyFactory policyFactory; bool loopEnabled{ false }; + bool variableSpeed{ false }; }; struct AudioIODiagnostics{ diff --git a/libraries/lib-sample-track/Mix.cpp b/libraries/lib-sample-track/Mix.cpp index b7442e8bfda311041c1aaca6d138127fc5309c7d..f4091af616742581dac54046e038f12e31a80217 100644 --- a/libraries/lib-sample-track/Mix.cpp +++ b/libraries/lib-sample-track/Mix.cpp @@ -62,8 +62,8 @@ Mixer::WarpOptions::WarpOptions(const BoundedEnvelope *e) : envelope(e), minSpeed(0.0), maxSpeed(0.0) {} -Mixer::WarpOptions::WarpOptions(double min, double max) - : minSpeed(min), maxSpeed(max) +Mixer::WarpOptions::WarpOptions(double min, double max, double initial) + : minSpeed{min}, maxSpeed{max}, initialSpeed{initial} { if (minSpeed < 0) { @@ -123,7 +123,7 @@ Mixer::Mixer(const SampleTrackConstArray &inputTracks, mTime = startTime; mBufferSize = outBufferSize; mInterleaved = outInterleaved; - mSpeed = 1.0; + mSpeed = warpOptions.initialSpeed; if( mixerSpec && mixerSpec->GetNumChannels() == mNumChannels && mixerSpec->GetNumTracks() == mNumInputTracks ) mMixerSpec = mixerSpec; @@ -627,12 +627,6 @@ void Mixer::SetTimesAndSpeed(double t0, double t1, double speed, bool bSkipping) Reposition(t0, bSkipping); } -void Mixer::SetSpeedForPlayAtSpeed(double speed) -{ - wxASSERT(std::isfinite(speed)); - mSpeed = fabs(speed); -} - void Mixer::SetSpeedForKeyboardScrubbing(double speed, double startTime) { wxASSERT(std::isfinite(speed)); diff --git a/libraries/lib-sample-track/Mix.h b/libraries/lib-sample-track/Mix.h index e89ea6686e4372c900b68efeb6d1da563e4d150e..a06f2761ea42cf3e12464c9138a9f619bb96ca42 100644 --- a/libraries/lib-sample-track/Mix.h +++ b/libraries/lib-sample-track/Mix.h @@ -78,12 +78,13 @@ class SAMPLE_TRACK_API Mixer { explicit WarpOptions(const BoundedEnvelope *e); //! Construct with no time warp - WarpOptions(double min, double max); + WarpOptions(double min, double max, double initial = 1.0); private: friend class Mixer; const BoundedEnvelope *envelope = nullptr; double minSpeed, maxSpeed; + double initialSpeed{ 1.0 }; }; // @@ -121,7 +122,6 @@ class SAMPLE_TRACK_API Mixer { // Used in scrubbing and other nonuniform playback policies. void SetTimesAndSpeed( double t0, double t1, double speed, bool bSkipping = false); - void SetSpeedForPlayAtSpeed(double speed); void SetSpeedForKeyboardScrubbing(double speed, double startTime); /// Current time in seconds (unwarped, i.e. always between startTime and stopTime) diff --git a/libraries/lib-screen-geometry/ViewInfo.cpp b/libraries/lib-screen-geometry/ViewInfo.cpp index ef4af54be60c6308164971f058197f254e24f5fe..664dd6df294fde12a2ae26bfa7f2e7f77cfac3be 100644 --- a/libraries/lib-screen-geometry/ViewInfo.cpp +++ b/libraries/lib-screen-geometry/ViewInfo.cpp @@ -158,7 +158,6 @@ wxDEFINE_EVENT( EVT_PLAY_REGION_CHANGE, PlayRegionEvent ); PlayRegionEvent::PlayRegionEvent( wxEventType commandType, PlayRegion *pReg ) : wxEvent{ 0, commandType } -, pRegion{ pReg } {} wxEvent *PlayRegionEvent::Clone() const diff --git a/libraries/lib-screen-geometry/ViewInfo.h b/libraries/lib-screen-geometry/ViewInfo.h index 4390d6c497c5356fc01aab984208d32176b89214..451c008702e67dfb498a7c1c1f96bb3be12419ce 100644 --- a/libraries/lib-screen-geometry/ViewInfo.h +++ b/libraries/lib-screen-geometry/ViewInfo.h @@ -118,8 +118,6 @@ struct PlayRegionEvent : public wxEvent { PlayRegionEvent( wxEventType commandType, PlayRegion *pRegion ); wxEvent *Clone() const override; - - wxWeakRef< PlayRegion > pRegion; }; wxDECLARE_EXPORTED_EVENT( SCREEN_GEOMETRY_API, diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index f19c49bff8975443d6361ecbb6b32f76dbaaa6a0..9a10da80dbf20e28408ca11a5bf8bb1d39c83b69 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -119,8 +119,6 @@ time warp info and AudioIOListener and whether the playback is looped. #include "Prefs.h" #include "Project.h" #include "ProjectWindows.h" -#include "TransactionScope.h" -#include "ViewInfo.h" // for PlayRegionEvent #include "WaveTrack.h" #include "TransactionScope.h" @@ -686,27 +684,13 @@ void AudioIO::SetOwningProject( } mOwningProject = pProject; - - if (pProject) - ViewInfo::Get( *pProject ).playRegion.Bind( - EVT_PLAY_REGION_CHANGE, AudioIO::LoopPlayUpdate); } void AudioIO::ResetOwningProject() { - if ( auto pOwningProject = mOwningProject.lock() ) - ViewInfo::Get( *pOwningProject ).playRegion.Unbind( - EVT_PLAY_REGION_CHANGE, AudioIO::LoopPlayUpdate); - mOwningProject.reset(); } -void AudioIO::LoopPlayUpdate( PlayRegionEvent &evt ) -{ - evt.Skip(); - AudioIO::Get()->mPlaybackSchedule.MessageProducer( evt ); -} - void AudioIO::StartMonitoring( const AudioIOStartStreamOptions &options ) { if ( mPortStreamV19 || mStreamToken ) diff --git a/src/AudioIO.h b/src/AudioIO.h index 30ef3d2c787860d64d7de0d0de528c4a09700804..f5969c03a7f8d970b6995c334bf04b7aa6e92f1c 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -36,7 +36,6 @@ class RingBuffer; class Mixer; class Resample; class AudioThread; -class PlayRegionEvent; class AudacityProject; @@ -513,7 +512,6 @@ private: void SetOwningProject( const std::shared_ptr<AudacityProject> &pProject ); void ResetOwningProject(); - static void LoopPlayUpdate( PlayRegionEvent &evt ); /*! Called in a loop from another worker thread that does not have the low-latency constraints diff --git a/src/PlaybackSchedule.cpp b/src/PlaybackSchedule.cpp index 27c6f863cf0e274da120853a7ab00d4ba4375b3f..531b8ec35e3134afa039ae9327777c725f3be90c 100644 --- a/src/PlaybackSchedule.cpp +++ b/src/PlaybackSchedule.cpp @@ -13,7 +13,10 @@ #include "AudioIOBase.h" #include "Envelope.h" #include "Mix.h" +#include "Project.h" +#include "ProjectSettings.h" #include "SampleCount.h" +#include "ViewInfo.h" // for PlayRegionEvent #include <cmath> @@ -146,11 +149,14 @@ const PlaybackPolicy &PlaybackSchedule::GetPolicy() const return const_cast<PlaybackSchedule&>(*this).GetPolicy(); } -NewDefaultPlaybackPolicy::NewDefaultPlaybackPolicy( - double trackEndTime, double loopEndTime, bool loopEnabled ) - : mTrackEndTime{ trackEndTime } +NewDefaultPlaybackPolicy::NewDefaultPlaybackPolicy( AudacityProject &project, + double trackEndTime, double loopEndTime, + bool loopEnabled, bool variableSpeed ) + : mProject{ project } + , mTrackEndTime{ trackEndTime } , mLoopEndTime{ loopEndTime } , mLoopEnabled{ loopEnabled } + , mVariableSpeed{ variableSpeed } {} NewDefaultPlaybackPolicy::~NewDefaultPlaybackPolicy() = default; @@ -159,16 +165,33 @@ void NewDefaultPlaybackPolicy::Initialize( PlaybackSchedule &schedule, double rate ) { PlaybackPolicy::Initialize(schedule, rate); - schedule.mMessageChannel.Write( { + mLastPlaySpeed = GetPlaySpeed(); + mMessageChannel.Write( { mLastPlaySpeed, schedule.mT0, mLoopEndTime, mLoopEnabled } ); + + ViewInfo::Get( mProject ).playRegion.Bind( EVT_PLAY_REGION_CHANGE, + &NewDefaultPlaybackPolicy::OnPlayRegionChange, this); + if (mVariableSpeed) + mProject.Bind( EVT_PROJECT_SETTINGS_CHANGE, + &NewDefaultPlaybackPolicy::OnPlaySpeedChange, this); +} + +Mixer::WarpOptions NewDefaultPlaybackPolicy::MixerWarpOptions( + PlaybackSchedule &schedule) +{ + if (mVariableSpeed) + // Enable variable rate mixing + return Mixer::WarpOptions(0.01, 32.0, GetPlaySpeed()); + else + return PlaybackPolicy::MixerWarpOptions(schedule); } PlaybackPolicy::BufferTimes NewDefaultPlaybackPolicy::SuggestedBufferTimes(PlaybackSchedule &) { // Shorter times than in the default policy so that responses to changes of - // selection don't lag too much - return { 0.5, 0.5, 1.0 }; + // loop region or speed slider don't lag too much + return { 0.05, 0.05, 0.25 }; } bool NewDefaultPlaybackPolicy::RevertToOldDefault(const PlaybackSchedule &schedule) const @@ -190,16 +213,17 @@ PlaybackSlice NewDefaultPlaybackPolicy::GetPlaybackSlice( PlaybackSchedule &schedule, size_t available) { + // How many samples to produce for each channel. + const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining()); + mRemaining = realTimeRemaining * mRate; + if (RevertToOldDefault(schedule)) return PlaybackPolicy::GetPlaybackSlice(schedule, available); - // How many samples to produce for each channel. - const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining()); auto frames = available; auto toProduce = frames; double deltat = frames / mRate; - mRemaining = realTimeRemaining * mRate; if (deltat > realTimeRemaining) { toProduce = frames = mRemaining; @@ -222,7 +246,7 @@ NewDefaultPlaybackPolicy::GetPlaybackSlice( std::pair<double, double> NewDefaultPlaybackPolicy::AdvancedTrackTime( PlaybackSchedule &schedule, double trackTime, size_t nSamples ) { - if (RevertToOldDefault(schedule)) + if (!mVariableSpeed && RevertToOldDefault(schedule)) return PlaybackPolicy::AdvancedTrackTime(schedule, trackTime, nSamples); mRemaining -= std::min(mRemaining, nSamples); @@ -234,7 +258,7 @@ std::pair<double, double> NewDefaultPlaybackPolicy::AdvancedTrackTime( if ( fabs(schedule.mT0 - schedule.mT1) < 1e-9 ) return {schedule.mT0, schedule.mT0}; - auto realDuration = nSamples / mRate; + auto realDuration = (nSamples / mRate) * mLastPlaySpeed; if (schedule.ReversedTime()) realDuration *= -1.0; @@ -252,7 +276,13 @@ bool NewDefaultPlaybackPolicy::RepositionPlayback( size_t frames, size_t available ) { // This executes in the TrackBufferExchange thread - auto data = schedule.mMessageChannel.Read(); + auto data = mMessageChannel.Read(); + + bool speedChange = false; + if (mVariableSpeed) { + speedChange = (mLastPlaySpeed != data.mPlaySpeed); + mLastPlaySpeed = data.mPlaySpeed; + } bool empty = (data.mT0 >= data.mT1); bool kicked = false; @@ -270,6 +300,7 @@ bool NewDefaultPlaybackPolicy::RepositionPlayback( // Four cases: looping transitions off, or transitions on, or stays on, // or stays off. + // Besides which, the variable speed slider may have changed. // If looping transitions on, or remains on and the region changed, // adjust the schedule... @@ -303,6 +334,9 @@ bool NewDefaultPlaybackPolicy::RepositionPlayback( const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining()); mRemaining = realTimeRemaining * mRate; } + else if (speedChange) + // Don't return early + kicked = true; else { // ... else the region did not change, or looping is now off, in // which case we have nothing special to do @@ -317,7 +351,8 @@ bool NewDefaultPlaybackPolicy::RepositionPlayback( { // Looping jumps left for (auto &pMixer : playbackMixers) - pMixer->SetTimesAndSpeed( schedule.mT0, schedule.mT1, 1.0, true ); + pMixer->SetTimesAndSpeed( + schedule.mT0, schedule.mT1, mLastPlaySpeed, true ); schedule.RealTimeRestart(); } else if (kicked) @@ -326,7 +361,7 @@ bool NewDefaultPlaybackPolicy::RepositionPlayback( const auto time = schedule.mTimeQueue.GetLastTime(); for (auto &pMixer : playbackMixers) { // So that the mixer will fetch the next samples from the right place: - pMixer->SetTimesAndSpeed( time, schedule.mT1, 1.0 ); + pMixer->SetTimesAndSpeed( time, schedule.mT1, mLastPlaySpeed ); pMixer->Reposition(time, true); } } @@ -338,6 +373,34 @@ bool NewDefaultPlaybackPolicy::Looping( const PlaybackSchedule & ) const return mLoopEnabled; } +void NewDefaultPlaybackPolicy::OnPlayRegionChange( PlayRegionEvent &evt) +{ + // This executes in the main thread + evt.Skip(); // Let other listeners hear the event too + WriteMessage(); +} + +void NewDefaultPlaybackPolicy::OnPlaySpeedChange(wxCommandEvent &evt) +{ + evt.Skip(); // Let other listeners hear the event too + WriteMessage(); +} + +void NewDefaultPlaybackPolicy::WriteMessage() +{ + const auto ®ion = ViewInfo::Get( mProject ).playRegion; + mMessageChannel.Write( { GetPlaySpeed(), + region.GetStart(), region.GetEnd(), region.Active() + } ); +} + +double NewDefaultPlaybackPolicy::GetPlaySpeed() +{ + return mVariableSpeed + ? ProjectSettings::Get(mProject).GetPlaySpeed() + : 1.0; +} + void PlaybackSchedule::Init( const double t0, const double t1, const AudioIOStartStreamOptions &options, @@ -369,14 +432,12 @@ void PlaybackSchedule::Init( SetTrackTime( mT0 ); if (options.policyFactory) - mpPlaybackPolicy = options.policyFactory(); + mpPlaybackPolicy = options.policyFactory(options); mWarpedTime = 0.0; mWarpedLength = RealDuration(mT1); mPolicyValid.store(true, std::memory_order_release); - - mMessageChannel.Initialize(); } double PlaybackSchedule::ComputeWarpedLength(double t0, double t1) const @@ -549,17 +610,3 @@ void PlaybackSchedule::TimeQueue::Prime(double time) if ( !mData.empty() ) mData[0].timeValue = time; } - -#include "ViewInfo.h" -void PlaybackSchedule::MessageProducer( PlayRegionEvent &evt) -{ - // This executes in the main thread - auto *pRegion = evt.pRegion.get(); - if ( !pRegion ) - return; - const auto ®ion = *pRegion; - - mMessageChannel.Write( { - region.GetStart(), region.GetEnd(), region.Active() - } ); -} diff --git a/src/PlaybackSchedule.h b/src/PlaybackSchedule.h index 55d4c30b54c1dc044da0eecadd8ddb9bfa22d8a2..5363aaff8d7af6dc77f95fa9ff5ceeb777a1cc9d 100644 --- a/src/PlaybackSchedule.h +++ b/src/PlaybackSchedule.h @@ -17,6 +17,9 @@ #include <chrono> #include <vector> +#include <wx/event.h> + +class AudacityProject; struct AudioIOStartStreamOptions; class BoundedEnvelope; using PRCrossfadeData = std::vector< std::vector < float > >; @@ -354,15 +357,6 @@ struct AUDACITY_DLL_API PlaybackSchedule { PlaybackPolicy &GetPolicy(); const PlaybackPolicy &GetPolicy() const; - // The main thread writes changes in response to user events, and - // the audio thread later reads, and changes the playback. - struct SlotData { - double mT0; - double mT1; - bool mLoopEnabled; - }; - MessageBuffer<SlotData> mMessageChannel; - void Init( double t0, double t1, const AudioIOStartStreamOptions &options, @@ -388,8 +382,6 @@ struct AUDACITY_DLL_API PlaybackSchedule { */ double SolveWarpedLength(double t0, double length) const; - void MessageProducer( PlayRegionEvent &evt ); - /** \brief True if the end time is before the start time */ bool ReversedTime() const { @@ -437,14 +429,21 @@ private: std::atomic<bool> mPolicyValid{ false }; }; -class NewDefaultPlaybackPolicy final : public PlaybackPolicy { +class NewDefaultPlaybackPolicy final + : public PlaybackPolicy + , public NonInterferingBase + , public wxEvtHandler +{ public: - NewDefaultPlaybackPolicy( - double trackEndTime, double loopEndTime, bool loopEnabled); + NewDefaultPlaybackPolicy( AudacityProject &project, + double trackEndTime, double loopEndTime, + bool loopEnabled, bool variableSpeed); ~NewDefaultPlaybackPolicy() override; void Initialize( PlaybackSchedule &schedule, double rate ) override; + Mixer::WarpOptions MixerWarpOptions(PlaybackSchedule &schedule) override; + BufferTimes SuggestedBufferTimes(PlaybackSchedule &schedule) override; bool Done( PlaybackSchedule &schedule, unsigned long ) override; @@ -464,11 +463,29 @@ public: private: bool RevertToOldDefault( const PlaybackSchedule &schedule ) const; + void OnPlayRegionChange(PlayRegionEvent &evt); + void OnPlaySpeedChange(wxCommandEvent &evt); + void WriteMessage(); + double GetPlaySpeed(); + + AudacityProject &mProject; + + // The main thread writes changes in response to user events, and + // the audio thread later reads, and changes the playback. + struct SlotData { + double mPlaySpeed; + double mT0; + double mT1; + bool mLoopEnabled; + }; + MessageBuffer<SlotData> mMessageChannel; + double mLastPlaySpeed{ 1.0 }; const double mTrackEndTime; double mLoopEndTime; size_t mRemaining{ 0 }; bool mProgress{ true }; bool mLoopEnabled{ true }; + bool mVariableSpeed{ false }; }; #endif diff --git a/src/ProjectAudioManager.cpp b/src/ProjectAudioManager.cpp index 1964eadef3fd9491c7aea05a7b622c9079b48c86..7c68cea53c58dd2db0cd01d1087d419aadd46f29 100644 --- a/src/ProjectAudioManager.cpp +++ b/src/ProjectAudioManager.cpp @@ -419,7 +419,7 @@ int ProjectAudioManager::PlayPlayRegion(const SelectedRegion &selectedRegion, std::swap(tcp0, tcp1); AudioIOStartStreamOptions myOptions = options; myOptions.policyFactory = - [tless, diff]() -> std::unique_ptr<PlaybackPolicy> { + [tless, diff](auto&) -> std::unique_ptr<PlaybackPolicy> { return std::make_unique<CutPreviewPlaybackPolicy>(tless, diff); }; token = gAudioIO->StartStream( @@ -1191,9 +1191,14 @@ DefaultPlayOptions( AudacityProject &project, bool newDefault ) if (newDefault) { const double trackEndTime = TrackList::Get(project).GetEndTime(); const double loopEndTime = ViewInfo::Get(project).playRegion.GetEnd(); - options.policyFactory = [trackEndTime, loopEndTime, loopEnabled]() -> std::unique_ptr<PlaybackPolicy> { - return std::make_unique<NewDefaultPlaybackPolicy>( - trackEndTime, loopEndTime, loopEnabled); }; + options.policyFactory = [&project, trackEndTime, loopEndTime]( + const AudioIOStartStreamOptions &options) + -> std::unique_ptr<PlaybackPolicy> + { + return std::make_unique<NewDefaultPlaybackPolicy>( project, + trackEndTime, loopEndTime, + options.loopEnabled, options.variableSpeed); + }; // Start play from left edge of selection options.pStartTime.emplace(ViewInfo::Get(project).selectedRegion.t0()); diff --git a/src/ProjectSettings.cpp b/src/ProjectSettings.cpp index a90b8401a89f6d898fba9563ce4fb6398f635664..970aa3c2ebc982b5dcb7559d1fd816af4c01ee19 100644 --- a/src/ProjectSettings.cpp +++ b/src/ProjectSettings.cpp @@ -140,6 +140,14 @@ void ProjectSettings::SetBandwidthSelectionFormatName( mBandwidthSelectionFormatName = formatName; } +void ProjectSettings::SetPlaySpeed(double value) +{ + if (auto oldValue = GetPlaySpeed(); value != oldValue) { + mPlaySpeed.store( value, std::memory_order_relaxed ); + Notify( mProject, ChangedPlaySpeed, oldValue ); + } +} + void ProjectSettings::SetSelectionFormat(const NumericFormatSymbol & format) { mSelectionFormat = format; diff --git a/src/ProjectSettings.h b/src/ProjectSettings.h index e22afdbe6bf4f2c5cd08c8b9e455b7c0f6ea58ef..ecfaff54f064230915b3f1ada5a4226ce62a7f60 100644 --- a/src/ProjectSettings.h +++ b/src/ProjectSettings.h @@ -65,8 +65,8 @@ public: // Values retrievable from GetInt() of the event for settings change enum EventCode : int { ChangedSyncLock, - ChangedProjectRate, - ChangedTool + ChangedTool, + ChangedPlaySpeed, }; explicit ProjectSettings( AudacityProject &project ); @@ -106,9 +106,8 @@ public: // Speed play double GetPlaySpeed() const { return mPlaySpeed.load( std::memory_order_relaxed ); } - void SetPlaySpeed( double value ) { - mPlaySpeed.store( value, std::memory_order_relaxed ); } - + void SetPlaySpeed( double value ); + // Selection Format void SetSelectionFormat(const NumericFormatSymbol & format); const NumericFormatSymbol & GetSelectionFormat() const; diff --git a/src/ScrubState.cpp b/src/ScrubState.cpp index 691512f29ef567cf555421120e00469e62256d6f..72c2e9399a7c7a4779a8a9d403d1f1b63e0d0001 100644 --- a/src/ScrubState.cpp +++ b/src/ScrubState.cpp @@ -55,8 +55,7 @@ struct ScrubQueue : NonInterferingBase // Make some initial silence. This is not needed in the case of // keyboard scrubbing or play-at-speed, because the initial speed // is known when this function is called the first time. - if ( !(message.options.isKeyboardScrubbing || - message.options.isPlayingAtSpeed) ) { + if ( !(message.options.isKeyboardScrubbing) ) { mData.mS0 = mData.mS1 = s0Init; mData.mGoal = -1; mData.mDuration = duration = inDuration; @@ -64,8 +63,7 @@ struct ScrubQueue : NonInterferingBase } } - if (mStarted || message.options.isKeyboardScrubbing || - message.options.isPlayingAtSpeed) { + if (mStarted || message.options.isKeyboardScrubbing) { Data newData; inDuration += mAccumulatedSeekDuration; @@ -353,11 +351,7 @@ bool ScrubbingPlaybackPolicy::AllowSeek( PlaybackSchedule & ) bool ScrubbingPlaybackPolicy::Done( PlaybackSchedule &schedule, unsigned long ) { - if (mOptions.isPlayingAtSpeed) - // some leftover length allowed in this case; ignore outputFrames - return PlaybackPolicy::Done(schedule, 0); - else - return false; + return false; } std::chrono::milliseconds @@ -451,9 +445,7 @@ bool ScrubbingPlaybackPolicy::RepositionPlayback( if (!mSilentScrub) { for (auto &pMixer : playbackMixers) { - if (mOptions.isPlayingAtSpeed) - pMixer->SetSpeedForPlayAtSpeed(mScrubSpeed); - else if (mOptions.isKeyboardScrubbing) + if (mOptions.isKeyboardScrubbing) pMixer->SetSpeedForKeyboardScrubbing(mScrubSpeed, startTime); else pMixer->SetTimesAndSpeed( diff --git a/src/ScrubState.h b/src/ScrubState.h index c0004fd01161bea4ee066662753448dfdcab7d30..300ee5e4e1eefaa9ec7fe9d97aa6b0e14908910e 100644 --- a/src/ScrubState.h +++ b/src/ScrubState.h @@ -25,7 +25,6 @@ struct ScrubbingOptions { double minTime {}; bool bySpeed {}; - bool isPlayingAtSpeed{}; bool isKeyboardScrubbing{}; double delay {}; diff --git a/src/menus/TransportMenus.cpp b/src/menus/TransportMenus.cpp index f42a755f6cb4474dbedf8da418637ff4c5826df6..ab95edc9b077ab41a5a3262490d834e7445452ba 100644 --- a/src/menus/TransportMenus.cpp +++ b/src/menus/TransportMenus.cpp @@ -1176,10 +1176,11 @@ BaseItemSharedPtr TransportMenu() return IsLoopingEnabled(project); } )), Command( wxT("ClearPlayRegion"), XXO("&Clear Loop"), - FN(OnClearPlayRegion), AlwaysEnabledFlag ), + FN(OnClearPlayRegion), AlwaysEnabledFlag, L"Shift+Alt+L" ), Command( wxT("SetPlayRegionToSelection"), XXO("&Set Loop to Selection"), - FN(OnSetPlayRegionToSelection), AlwaysEnabledFlag ), + FN(OnSetPlayRegionToSelection), AlwaysEnabledFlag, + L"Shift+L" ), Command( wxT("SetPlayRegionIn"), SetLoopInTitle, FN(OnSetPlayRegionIn), AlwaysEnabledFlag ), diff --git a/src/toolbars/TranscriptionToolBar.cpp b/src/toolbars/TranscriptionToolBar.cpp index 9fb868dfe446e29beac738c83e915dda0738d52e..73a25334649023d4481fd58ad28248f9aa44752e 100644 --- a/src/toolbars/TranscriptionToolBar.cpp +++ b/src/toolbars/TranscriptionToolBar.cpp @@ -491,11 +491,10 @@ void TranscriptionToolBar::PlayAtSpeed(bool newDefault, bool cutPreview) if ( TrackList::Get( *p ).Any< NoteTrack >() ) bFixedSpeedPlay = true; - // Scrubbing only supports straight through play. - // So if newDefault or cutPreview, we have to fall back to fixed speed. + // If cutPreview, we have to fall back to fixed speed. if (newDefault) cutPreview = false; - bFixedSpeedPlay = bFixedSpeedPlay || newDefault || cutPreview; + bFixedSpeedPlay = bFixedSpeedPlay || cutPreview; if (bFixedSpeedPlay) { // Create a BoundedEnvelope if we haven't done so already @@ -528,11 +527,12 @@ void TranscriptionToolBar::PlayAtSpeed(bool newDefault, bool cutPreview) // Start playing if (playRegion.GetStart() < 0) return; - if (bFixedSpeedPlay) + { auto options = DefaultPlayOptions( *p, newDefault ); // No need to set cutPreview options. - options.envelope = mEnvelope.get(); + options.envelope = bFixedSpeedPlay ? mEnvelope.get() : nullptr; + options.variableSpeed = !bFixedSpeedPlay; auto mode = cutPreview ? PlayMode::cutPreviewPlay : newDefault ? PlayMode::loopedPlay @@ -542,16 +542,10 @@ void TranscriptionToolBar::PlayAtSpeed(bool newDefault, bool cutPreview) options, mode); } - else - { - auto &scrubber = Scrubber::Get( *p ); - scrubber.StartSpeedPlay(GetPlaySpeed(), - playRegion.GetStart(), playRegion.GetEnd()); - } } // Come here from button clicks only -void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & WXUNUSED(event)) +void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & event) { auto button = mButtons[TTB_PlaySpeed]; @@ -559,6 +553,7 @@ void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & WXUNUSED(event)) const bool cutPreview = mButtons[TTB_PlaySpeed]->WasControlDown(); const bool looped = !cutPreview && !button->WasShiftDown(); + OnSpeedSlider(event); PlayAtSpeed(looped, cutPreview); } diff --git a/src/tracks/ui/Scrubbing.cpp b/src/tracks/ui/Scrubbing.cpp index 888aae0fc3c3941ccb438b4560cd68e66c34d678..0f33d37d9913b5af464c6813c7ea577d30f50525 100644 --- a/src/tracks/ui/Scrubbing.cpp +++ b/src/tracks/ui/Scrubbing.cpp @@ -348,7 +348,7 @@ void Scrubber::MarkScrubStart( static AudioIOStartStreamOptions::PolicyFactory ScrubbingPlaybackPolicyFactory(const ScrubbingOptions &options) { - return [options]() -> std::unique_ptr<PlaybackPolicy> + return [options](auto&) -> std::unique_ptr<PlaybackPolicy> { return std::make_unique<ScrubbingPlaybackPolicy>(options); }; @@ -423,7 +423,6 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx) options.playNonWaveTracks = false; options.envelope = nullptr; mOptions.delay = (ScrubPollInterval_ms / 1000.0); - mOptions.isPlayingAtSpeed = false; mOptions.isKeyboardScrubbing = false; mOptions.initSpeed = 0; mOptions.minSpeed = 0.0; @@ -499,96 +498,6 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx) } } - - -bool Scrubber::StartSpeedPlay(double speed, double time0, double time1) -{ - if (IsScrubbing()) - return false; - - auto gAudioIO = AudioIO::Get(); - 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; - } - - auto &projectAudioManager = ProjectAudioManager::Get( *mProject ); - if (busy) { - projectAudioManager.Stop(); - } - mScrubStartPosition = 0; - mSpeedPlaying = true; - mKeyboardScrubbing = false; - mMaxSpeed = speed; - mDragging = false; - - auto options = DefaultSpeedPlayOptions( *mProject ); - -#ifndef USE_SCRUB_THREAD - // Yuck, we either have to poll "by hand" when scrub polling doesn't - // work with a thread, or else yield to timer messages, but that would - // execute too much else - options.playbackStreamPrimer = [this](){ - ContinueScrubbingPoll(); - return ScrubPollInterval_ms; - }; -#endif - - options.playNonWaveTracks = false; - options.envelope = nullptr; - mOptions.delay = (ScrubPollInterval_ms / 1000.0); - mOptions.initSpeed = speed; - mOptions.minSpeed = speed -0.01; - mOptions.maxSpeed = speed +0.01; - - if (time1 == time0) - time1 = std::max(0.0, TrackList::Get( *mProject ).GetEndTime()); - mOptions.minTime = 0; - mOptions.maxTime = time1; - mOptions.minStutterTime = std::max(0.0, MinStutter); - mOptions.bySpeed = true; - mOptions.adjustStart = false; - mOptions.isPlayingAtSpeed = true; - mOptions.isKeyboardScrubbing = false; - - 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 - - // Must start the thread and poller first or else PlayPlayRegion - // will insert some silence - StartPolling(); - auto cleanup = finally([this]{ - if (mScrubToken < 0) - StopPolling(); - }); - - mScrubSpeedDisplayCountdown = 0; - // Aim to stop within 20 samples of correct position. - double stopTolerance = 20.0 / options.rate; - options.policyFactory = ScrubbingPlaybackPolicyFactory(mOptions); - mScrubToken = - // Reduce time by 'stopTolerance' fudge factor, so that the Play will stop. - projectAudioManager.PlayPlayRegion( - SelectedRegion(time0, time1-stopTolerance), options, - PlayMode::normalPlay, backwards); - - if (mScrubToken >= 0) { - mLastScrubPosition = 0; - } - - return true; -} - - bool Scrubber::StartKeyboardScrubbing(double time0, bool backwards) { if (HasMark() || AudioIO::Get()->IsBusy()) @@ -630,7 +539,6 @@ bool Scrubber::StartKeyboardScrubbing(double time0, bool backwards) mOptions.maxTime = std::max(0.0, TrackList::Get(*mProject).GetEndTime()); mOptions.bySpeed = true; mOptions.adjustStart = false; - mOptions.isPlayingAtSpeed = false; mOptions.isKeyboardScrubbing = true; // Must start the thread and poller first or else PlayPlayRegion diff --git a/src/tracks/ui/Scrubbing.h b/src/tracks/ui/Scrubbing.h index ab31d888e85b01af3e802e3f78424bda0b8518bd..a32453a38b6e97b575ec9709d5ab8383ef25d571 100644 --- a/src/tracks/ui/Scrubbing.h +++ b/src/tracks/ui/Scrubbing.h @@ -61,7 +61,6 @@ 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); bool StartKeyboardScrubbing(double time0, bool backwards); double GetKeyboardScrubbingSpeed();